Skip to content

Commit 6d5e8bb

Browse files
committed
Homogenize commands through Scaffolder interface
Signed-off-by: Adrian Orive <[email protected]>
1 parent dc5ea0c commit 6d5e8bb

32 files changed

+1677
-1514
lines changed

cmd/alpha.go

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,10 @@ import (
2020
"github.com/spf13/cobra"
2121
)
2222

23-
// newAlphaCommand returns alpha subcommand which will be mounted
24-
// at the root command by the caller.
25-
func newAlphaCommand() *cobra.Command {
26-
cmd := &cobra.Command{
23+
func newAlphaCmd() *cobra.Command {
24+
return &cobra.Command{
2725
Use: "alpha",
2826
Short: "Expose commands which are in experimental or early stages of development",
2927
Long: `Command group for commands which are either experimental or in early stages of development`,
30-
Example: `
31-
# scaffolds webhook server
32-
kubebuilder alpha webhook <params>
33-
`,
3428
}
35-
36-
cmd.AddCommand(
37-
newWebhookCmd(),
38-
)
39-
return cmd
4029
}

cmd/api.go

Lines changed: 145 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -18,157 +18,205 @@ package main
1818

1919
import (
2020
"bufio"
21+
"errors"
2122
"fmt"
2223
"log"
2324
"os"
24-
"os/exec"
2525
"strings"
2626

2727
"github.com/spf13/cobra"
2828
flag "github.com/spf13/pflag"
2929

3030
"sigs.k8s.io/kubebuilder/cmd/internal"
31-
"sigs.k8s.io/kubebuilder/cmd/util"
31+
"sigs.k8s.io/kubebuilder/internal/config"
3232
"sigs.k8s.io/kubebuilder/pkg/scaffold"
3333
"sigs.k8s.io/kubebuilder/pkg/scaffold/resource"
3434
"sigs.k8s.io/kubebuilder/plugins/addon"
3535
)
3636

37-
type apiOptions struct {
38-
apiScaffolder scaffold.API
39-
resourceFlag, controllerFlag *flag.Flag
37+
type apiError struct {
38+
err error
39+
}
4040

41-
// runMake indicates whether to run make or not after scaffolding APIs
42-
runMake bool
41+
func (e apiError) Error() string {
42+
return fmt.Sprintf("failed to create API: %v", e.err)
43+
}
44+
45+
func newAPICmd() *cobra.Command {
46+
options := &apiOptions{}
47+
48+
cmd := &cobra.Command{
49+
Use: "api",
50+
Short: "Scaffold a Kubernetes API",
51+
Long: `Scaffold a Kubernetes API by creating a Resource definition and / or a Controller.
52+
53+
kubebuilder create api will prompt the user asking if it should scaffold the Resource and / or Controller. To only
54+
scaffold a Controller for an existing Resource, select "n" for Resource. To only define
55+
the schema for a Resource without writing a Controller, select "n" for Controller.
56+
57+
After the scaffold is written, api will run make on the project.
58+
`,
59+
Example: ` # Create a frigates API with Group: ship, Version: v1beta1 and Kind: Frigate
60+
kubebuilder create api --group ship --version v1beta1 --kind Frigate
61+
62+
# Edit the API Scheme
63+
nano api/v1beta1/frigate_types.go
64+
65+
# Edit the Controller
66+
nano controllers/frigate/frigate_controller.go
67+
68+
# Edit the Controller Test
69+
nano controllers/frigate/frigate_controller_test.go
70+
71+
# Install CRDs into the Kubernetes cluster using kubectl apply
72+
make install
73+
74+
# Regenerate code and run against the Kubernetes cluster configured by ~/.kube/config
75+
make run
76+
`,
77+
Run: func(_ *cobra.Command, _ []string) {
78+
if err := run(options); err != nil {
79+
log.Fatal(apiError{err})
80+
}
81+
},
82+
}
83+
84+
options.bindFlags(cmd)
85+
86+
return cmd
87+
}
4388

89+
var _ commandOptions = &apiOptions{}
90+
91+
type apiOptions struct {
4492
// pattern indicates that we should use a plugin to build according to a pattern
4593
pattern string
94+
95+
resource *resource.Resource
96+
97+
// Check if we have to scaffold resource and/or controller
98+
resourceFlag *flag.Flag
99+
controllerFlag *flag.Flag
100+
doResource bool
101+
doController bool
102+
103+
// force indicates that the resource should be created even if it already exists
104+
force bool
105+
106+
// runMake indicates whether to run make or not after scaffolding APIs
107+
runMake bool
46108
}
47109

48-
func (o *apiOptions) bindCmdFlags(cmd *cobra.Command) {
49-
cmd.Flags().BoolVar(&o.runMake, "make", true,
50-
"if true, run make after generating files")
51-
cmd.Flags().BoolVar(&o.apiScaffolder.DoResource, "resource", true,
110+
func (o *apiOptions) bindFlags(cmd *cobra.Command) {
111+
cmd.Flags().BoolVar(&o.runMake, "make", true, "if true, run make after generating files")
112+
113+
cmd.Flags().BoolVar(&o.doResource, "resource", true,
52114
"if set, generate the resource without prompting the user")
53115
o.resourceFlag = cmd.Flag("resource")
54-
cmd.Flags().BoolVar(&o.apiScaffolder.DoController, "controller", true,
116+
cmd.Flags().BoolVar(&o.doController, "controller", true,
55117
"if set, generate the controller without prompting the user")
56118
o.controllerFlag = cmd.Flag("controller")
119+
57120
if os.Getenv("KUBEBUILDER_ENABLE_PLUGINS") != "" {
58121
cmd.Flags().StringVar(&o.pattern, "pattern", "",
59122
"generates an API following an extension pattern (addon)")
60123
}
61-
cmd.Flags().BoolVar(&o.apiScaffolder.Force, "force", false,
124+
125+
cmd.Flags().BoolVar(&o.force, "force", false,
62126
"attempt to create resource even if it already exists")
63-
o.apiScaffolder.Resource = resourceForFlags(cmd.Flags())
64-
}
65127

66-
// resourceForFlags registers flags for Resource fields and returns the Resource
67-
func resourceForFlags(f *flag.FlagSet) *resource.Resource {
68-
r := &resource.Resource{}
69-
f.StringVar(&r.Kind, "kind", "", "resource Kind")
70-
f.StringVar(&r.Group, "group", "", "resource Group")
71-
f.StringVar(&r.Version, "version", "", "resource Version")
72-
f.BoolVar(&r.Namespaced, "namespaced", true, "resource is namespaced")
73-
f.BoolVar(&r.CreateExampleReconcileBody, "example", true,
128+
o.resource = &resource.Resource{}
129+
cmd.Flags().StringVar(&o.resource.Kind, "kind", "", "resource Kind")
130+
cmd.Flags().StringVar(&o.resource.Group, "group", "", "resource Group")
131+
cmd.Flags().StringVar(&o.resource.Version, "version", "", "resource Version")
132+
cmd.Flags().BoolVar(&o.resource.Namespaced, "namespaced", true, "resource is namespaced")
133+
cmd.Flags().BoolVar(&o.resource.CreateExampleReconcileBody, "example", true,
74134
"if true an example reconcile body should be written while scaffolding a resource.")
75-
return r
76135
}
77136

78-
// APICmd represents the resource command
79-
func (o *apiOptions) runAddAPI() {
80-
internal.DieIfNotConfigured()
81-
82-
switch strings.ToLower(o.pattern) {
83-
case "":
84-
// Default pattern
85-
86-
case "addon":
87-
o.apiScaffolder.Plugins = append(o.apiScaffolder.Plugins, &addon.Plugin{})
88-
89-
default:
90-
log.Fatalf("unknown pattern %q", o.pattern)
137+
func (o *apiOptions) loadConfig() (*config.Config, error) {
138+
projectConfig, err := config.Load()
139+
if os.IsNotExist(err) {
140+
return nil, errors.New("unable to find configuration file, project must be initialized")
91141
}
92142

93-
if err := o.apiScaffolder.Validate(); err != nil {
94-
log.Fatalln(err)
143+
return projectConfig, err
144+
}
145+
146+
func (o *apiOptions) validate(c *config.Config) error {
147+
if err := o.resource.Validate(); err != nil {
148+
return err
95149
}
96150

97151
reader := bufio.NewReader(os.Stdin)
98152
if !o.resourceFlag.Changed {
99153
fmt.Println("Create Resource [y/n]")
100-
o.apiScaffolder.DoResource = util.YesNo(reader)
154+
o.doResource = internal.YesNo(reader)
101155
}
102-
103156
if !o.controllerFlag.Changed {
104157
fmt.Println("Create Controller [y/n]")
105-
o.apiScaffolder.DoController = util.YesNo(reader)
106-
}
107-
108-
fmt.Println("Writing scaffold for you to edit...")
109-
110-
if err := o.apiScaffolder.Scaffold(); err != nil {
111-
log.Fatal(err)
158+
o.doController = internal.YesNo(reader)
112159
}
113160

114-
if err := o.postScaffold(); err != nil {
115-
log.Fatal(err)
116-
}
117-
}
161+
// In case we want to scaffold a resource API we need to do some checks
162+
if o.doResource {
163+
// Skip the following check for v1 as resources aren't tracked
164+
if !c.IsV1() {
165+
// Check that resource doesn't exist or flag force was set
166+
if !o.force {
167+
resourceExists := false
168+
for _, r := range c.Resources {
169+
if r.Group == o.resource.Group &&
170+
r.Version == o.resource.Version &&
171+
r.Kind == o.resource.Kind {
172+
resourceExists = true
173+
break
174+
}
175+
}
176+
if resourceExists {
177+
return errors.New("API resource already exists")
178+
}
179+
}
180+
}
118181

119-
func (o *apiOptions) postScaffold() error {
120-
if o.runMake {
121-
fmt.Println("Running make...")
122-
cm := exec.Command("make") // #nosec
123-
cm.Stderr = os.Stderr
124-
cm.Stdout = os.Stdout
125-
if err := cm.Run(); err != nil {
126-
return fmt.Errorf("error running make: %v", err)
182+
// The following check is v2 specific as multi-group isn't enabled by default
183+
if c.IsV2() {
184+
// Check the group is the same for single-group projects
185+
if !c.MultiGroup {
186+
validGroup := true
187+
for _, existingGroup := range c.ResourceGroups() {
188+
if !strings.EqualFold(o.resource.Group, existingGroup) {
189+
validGroup = false
190+
break
191+
}
192+
}
193+
if !validGroup {
194+
return fmt.Errorf("multiple groups are not allowed by default, to enable multi-group visit %s",
195+
"kubebuilder.io/migration/multi-group.html")
196+
}
197+
}
127198
}
128199
}
200+
129201
return nil
130202
}
131203

132-
func newAPICommand() *cobra.Command {
133-
options := apiOptions{
134-
apiScaffolder: scaffold.API{},
135-
}
136-
137-
apiCmd := &cobra.Command{
138-
Use: "api",
139-
Short: "Scaffold a Kubernetes API",
140-
Long: `Scaffold a Kubernetes API by creating a Resource definition and / or a Controller.
141-
142-
create resource will prompt the user for if it should scaffold the Resource and / or Controller. To only
143-
scaffold a Controller for an existing Resource, select "n" for Resource. To only define
144-
the schema for a Resource without writing a Controller, select "n" for Controller.
145-
146-
After the scaffold is written, api will run make on the project.
147-
`,
148-
Example: ` # Create a frigates API with Group: ship, Version: v1beta1 and Kind: Frigate
149-
kubebuilder create api --group ship --version v1beta1 --kind Frigate
150-
151-
# Edit the API Scheme
152-
nano api/v1beta1/frigate_types.go
153-
154-
# Edit the Controller
155-
nano controllers/frigate/frigate_controller.go
156-
157-
# Edit the Controller Test
158-
nano controllers/frigate/frigate_controller_test.go
204+
func (o *apiOptions) scaffolder(c *config.Config) (scaffold.Scaffolder, error) {
205+
plugins := make([]scaffold.Plugin, 0)
206+
switch strings.ToLower(o.pattern) {
207+
case "":
208+
// Default pattern
159209

160-
# Install CRDs into the Kubernetes cluster using kubectl apply
161-
make install
210+
case "addon":
211+
plugins = append(plugins, &addon.Plugin{})
162212

163-
# Regenerate code and run against the Kubernetes cluster configured by ~/.kube/config
164-
make run
165-
`,
166-
Run: func(cmd *cobra.Command, args []string) {
167-
options.runAddAPI()
168-
},
213+
default:
214+
return nil, fmt.Errorf("unknown pattern %q", o.pattern)
169215
}
170216

171-
options.bindCmdFlags(apiCmd)
217+
return scaffold.NewAPIScaffolder(c, o.resource, o.doResource, o.doController, plugins), nil
218+
}
172219

173-
return apiCmd
220+
func (o *apiOptions) postScaffold(_ *config.Config) error {
221+
return internal.RunCmd("Running make", "make")
174222
}

cmd/create.go

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,13 @@ limitations under the License.
1717
package main
1818

1919
import (
20-
"fmt"
21-
2220
"github.com/spf13/cobra"
23-
24-
"sigs.k8s.io/kubebuilder/cmd/internal"
2521
)
2622

2723
func newCreateCmd() *cobra.Command {
28-
cmd := &cobra.Command{
24+
return &cobra.Command{
2925
Use: "create",
3026
Short: "Scaffold a Kubernetes API or webhook.",
3127
Long: `Scaffold a Kubernetes API or webhook.`,
32-
Run: func(cmd *cobra.Command, args []string) {
33-
fmt.Println("Coming soon.")
34-
},
3528
}
36-
cmd.AddCommand(
37-
newAPICommand(),
38-
)
39-
40-
if !internal.ConfiguredAndV1() {
41-
cmd.AddCommand(
42-
newWebhookV2Cmd(),
43-
)
44-
}
45-
46-
return cmd
4729
}

0 commit comments

Comments
 (0)