@@ -18,157 +18,205 @@ package main
18
18
19
19
import (
20
20
"bufio"
21
+ "errors"
21
22
"fmt"
22
23
"log"
23
24
"os"
24
- "os/exec"
25
25
"strings"
26
26
27
27
"github.com/spf13/cobra"
28
28
flag "github.com/spf13/pflag"
29
29
30
30
"sigs.k8s.io/kubebuilder/cmd/internal"
31
- "sigs.k8s.io/kubebuilder/cmd/util "
31
+ "sigs.k8s.io/kubebuilder/internal/config "
32
32
"sigs.k8s.io/kubebuilder/pkg/scaffold"
33
33
"sigs.k8s.io/kubebuilder/pkg/scaffold/resource"
34
34
"sigs.k8s.io/kubebuilder/plugins/addon"
35
35
)
36
36
37
- type apiOptions struct {
38
- apiScaffolder scaffold. API
39
- resourceFlag , controllerFlag * flag. Flag
37
+ type apiError struct {
38
+ err error
39
+ }
40
40
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
+ }
43
88
89
+ var _ commandOptions = & apiOptions {}
90
+
91
+ type apiOptions struct {
44
92
// pattern indicates that we should use a plugin to build according to a pattern
45
93
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
46
108
}
47
109
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 ,
52
114
"if set, generate the resource without prompting the user" )
53
115
o .resourceFlag = cmd .Flag ("resource" )
54
- cmd .Flags ().BoolVar (& o .apiScaffolder . DoController , "controller" , true ,
116
+ cmd .Flags ().BoolVar (& o .doController , "controller" , true ,
55
117
"if set, generate the controller without prompting the user" )
56
118
o .controllerFlag = cmd .Flag ("controller" )
119
+
57
120
if os .Getenv ("KUBEBUILDER_ENABLE_PLUGINS" ) != "" {
58
121
cmd .Flags ().StringVar (& o .pattern , "pattern" , "" ,
59
122
"generates an API following an extension pattern (addon)" )
60
123
}
61
- cmd .Flags ().BoolVar (& o .apiScaffolder .Force , "force" , false ,
124
+
125
+ cmd .Flags ().BoolVar (& o .force , "force" , false ,
62
126
"attempt to create resource even if it already exists" )
63
- o .apiScaffolder .Resource = resourceForFlags (cmd .Flags ())
64
- }
65
127
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 ,
74
134
"if true an example reconcile body should be written while scaffolding a resource." )
75
- return r
76
135
}
77
136
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" )
91
141
}
92
142
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
95
149
}
96
150
97
151
reader := bufio .NewReader (os .Stdin )
98
152
if ! o .resourceFlag .Changed {
99
153
fmt .Println ("Create Resource [y/n]" )
100
- o .apiScaffolder . DoResource = util .YesNo (reader )
154
+ o .doResource = internal .YesNo (reader )
101
155
}
102
-
103
156
if ! o .controllerFlag .Changed {
104
157
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 )
112
159
}
113
160
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
+ }
118
181
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
+ }
127
198
}
128
199
}
200
+
129
201
return nil
130
202
}
131
203
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
159
209
160
- # Install CRDs into the Kubernetes cluster using kubectl apply
161
- make install
210
+ case "addon" :
211
+ plugins = append ( plugins , & addon. Plugin {})
162
212
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 )
169
215
}
170
216
171
- options .bindCmdFlags (apiCmd )
217
+ return scaffold .NewAPIScaffolder (c , o .resource , o .doResource , o .doController , plugins ), nil
218
+ }
172
219
173
- return apiCmd
220
+ func (o * apiOptions ) postScaffold (_ * config.Config ) error {
221
+ return internal .RunCmd ("Running make" , "make" )
174
222
}
0 commit comments