@@ -18,7 +18,6 @@ package cli
18
18
19
19
import (
20
20
"fmt"
21
- "log"
22
21
"os"
23
22
"strings"
24
23
@@ -95,51 +94,33 @@ type cli struct { //nolint:maligned
95
94
// A filtered set of plugins that should be used by command constructors.
96
95
resolvedPlugins []plugin.Plugin
97
96
98
- // Whether some generic help should be printed, i.e. if the binary
99
- // was invoked outside of a project with incorrect flags or -h|--help.
100
- doHelp bool
101
-
102
97
// Root command.
103
98
cmd * cobra.Command
104
99
}
105
100
106
101
// New creates a new cli instance.
102
+ // Developer errors (e.g. not registering any plugins, extra commands with conflicting names) return an error
103
+ // while user errors (e.g. errors while parsing flags, unresolvable plugins) create a command which return the error.
107
104
func New (opts ... Option ) (CLI , error ) {
108
105
// Create the CLI.
109
106
c , err := newCLI (opts ... )
110
107
if err != nil {
111
108
return nil , err
112
109
}
113
110
114
- // Get project version and plugin keys.
115
- if err := c .getInfo (); err != nil {
116
- return nil , err
117
- }
118
-
119
- // Resolve plugins for project version and plugin keys.
120
- if err := c .resolve (); err != nil {
121
- return nil , err
111
+ // Build the cmd tree.
112
+ if err := c .buildCmd (); err != nil {
113
+ c .cmd .RunE = errCmdFunc (err )
114
+ return c , nil
122
115
}
123
116
124
- // Build the root command.
125
- c .cmd = c .buildRootCmd ()
126
-
127
117
// Add extra commands injected by options.
128
- for _ , cmd := range c .extraCommands {
129
- for _ , subCmd := range c .cmd .Commands () {
130
- if cmd .Name () == subCmd .Name () {
131
- return nil , fmt .Errorf ("command %q already exists" , cmd .Name ())
132
- }
133
- }
134
- c .cmd .AddCommand (cmd )
118
+ if err := c .addExtraCommands (); err != nil {
119
+ return nil , err
135
120
}
136
121
137
122
// Write deprecation notices after all commands have been constructed.
138
- for _ , p := range c .resolvedPlugins {
139
- if d , isDeprecated := p .(plugin.Deprecated ); isDeprecated {
140
- fmt .Printf (noticeColor , fmt .Sprintf (deprecationFmt , d .DeprecationWarning ()))
141
- }
142
- }
123
+ c .printDeprecationWarnings ()
143
124
144
125
return c , nil
145
126
}
@@ -166,37 +147,47 @@ func newCLI(opts ...Option) (*cli, error) {
166
147
}
167
148
168
149
// getInfoFromFlags obtains the project version and plugin keys from flags.
169
- func (c * cli ) getInfoFromFlags () (string , []string ) {
150
+ func (c * cli ) getInfoFromFlags () (string , []string , error ) {
170
151
// Partially parse the command line arguments
171
- fs := pflag .NewFlagSet ("base" , pflag .ExitOnError )
152
+ fs := pflag .NewFlagSet ("base" , pflag .ContinueOnError )
153
+
154
+ // Load the base command global flags
155
+ fs .AddFlagSet (c .cmd .PersistentFlags ())
172
156
173
157
// Omit unknown flags to avoid parsing errors
174
158
fs .ParseErrorsWhitelist = pflag.ParseErrorsWhitelist {UnknownFlags : true }
175
159
176
- // Define the flags needed for plugin resolution
177
- var projectVersion , plugins string
178
- var help bool
179
- fs .StringVar (& projectVersion , projectVersionFlag , "" , "project version" )
180
- fs .StringVar (& plugins , pluginsFlag , "" , "plugins to run" )
181
- fs .BoolVarP (& help , "help" , "h" , false , "help flag" )
160
+ // FlagSet special cases --help and -h, so we need to create a dummy flag with these 2 values to prevent the default
161
+ // behavior (printing the usage of this FlagSet) as we want to print the usage message of the underlying command.
162
+ fs .BoolP ("help" , "h" , false , fmt .Sprintf ("help for %s" , c .commandName ))
182
163
183
164
// Parse the arguments
184
- err := fs .Parse (os .Args [1 :])
165
+ if err := fs .Parse (os .Args [1 :]); err != nil {
166
+ return "" , []string {}, err
167
+ }
185
168
186
- // User needs *generic* help if args are incorrect or --help is set and
187
- // --project-version is not set. Plugin-specific help is given if a
188
- // plugin.Context is updated, which does not require this field.
189
- c .doHelp = err != nil || help && ! fs .Lookup (projectVersionFlag ).Changed
169
+ // Define the flags needed for plugin resolution
170
+ var (
171
+ projectVersion string
172
+ plugins []string
173
+ err error
174
+ )
175
+ // GetXxxxx methods will not yield errors because we know for certain these flags exist and types match.
176
+ projectVersion , err = fs .GetString (projectVersionFlag )
177
+ if err != nil {
178
+ return "" , []string {}, err
179
+ }
180
+ plugins , err = fs .GetStringSlice (pluginsFlag )
181
+ if err != nil {
182
+ return "" , []string {}, err
183
+ }
190
184
191
- // Split the comma-separated plugins
192
- var pluginSet []string
193
- if plugins != "" {
194
- for _ , p := range strings .Split (plugins , "," ) {
195
- pluginSet = append (pluginSet , strings .TrimSpace (p ))
196
- }
185
+ // Remove leading and trailing spaces
186
+ for i , key := range plugins {
187
+ plugins [i ] = strings .TrimSpace (key )
197
188
}
198
189
199
- return projectVersion , pluginSet
190
+ return projectVersion , plugins , nil
200
191
}
201
192
202
193
// getInfoFromConfigFile obtains the project version and plugin keys from the project config file.
@@ -293,14 +284,16 @@ func (c cli) resolveFlagsAndConfigFileConflicts(
293
284
// getInfo obtains the project version and plugin keys resolving conflicts among flags and the project config file.
294
285
func (c * cli ) getInfo () error {
295
286
// Get project version and plugin info from flags
296
- flagProjectVersion , flagPlugins := c .getInfoFromFlags ()
287
+ flagProjectVersion , flagPlugins , err := c .getInfoFromFlags ()
288
+ if err != nil {
289
+ return err
290
+ }
297
291
// Get project version and plugin info from project configuration file
298
292
cfgProjectVersion , cfgPlugins , _ := getInfoFromConfigFile ()
299
293
// We discard the error because not being able to read a project configuration file
300
294
// is not fatal for some commands. The ones that require it need to check its existence.
301
295
302
296
// Resolve project version and plugin keys
303
- var err error
304
297
c .projectVersion , c .pluginKeys , err = c .resolveFlagsAndConfigFileConflicts (
305
298
flagProjectVersion , cfgProjectVersion , flagPlugins , cfgPlugins ,
306
299
)
@@ -399,15 +392,13 @@ func (c *cli) resolve() error {
399
392
return nil
400
393
}
401
394
402
- // buildRootCmd returns a root command with a subcommand tree reflecting the
395
+ // addSubcommands returns a root command with a subcommand tree reflecting the
403
396
// current project's state.
404
- func (c cli ) buildRootCmd () * cobra.Command {
405
- rootCmd := c .defaultCommand ()
406
-
397
+ func (c * cli ) addSubcommands () {
407
398
// kubebuilder completion
408
399
// Only add completion if requested
409
400
if c .completionCommand {
410
- rootCmd .AddCommand (c .newCompletionCmd ())
401
+ c . cmd .AddCommand (c .newCompletionCmd ())
411
402
}
412
403
413
404
// kubebuilder create
@@ -416,76 +407,61 @@ func (c cli) buildRootCmd() *cobra.Command {
416
407
createCmd .AddCommand (c .newCreateAPICmd ())
417
408
createCmd .AddCommand (c .newCreateWebhookCmd ())
418
409
if createCmd .HasSubCommands () {
419
- rootCmd .AddCommand (createCmd )
410
+ c . cmd .AddCommand (createCmd )
420
411
}
421
412
422
413
// kubebuilder edit
423
- rootCmd .AddCommand (c .newEditCmd ())
414
+ c . cmd .AddCommand (c .newEditCmd ())
424
415
425
416
// kubebuilder init
426
- rootCmd .AddCommand (c .newInitCmd ())
417
+ c . cmd .AddCommand (c .newInitCmd ())
427
418
428
419
// kubebuilder version
429
420
// Only add version if a version string was provided
430
421
if c .version != "" {
431
- rootCmd .AddCommand (c .newVersionCmd ())
422
+ c . cmd .AddCommand (c .newVersionCmd ())
432
423
}
433
-
434
- return rootCmd
435
424
}
436
425
437
- // defaultCommand returns the root command without its subcommands.
438
- func (c cli ) defaultCommand () * cobra.Command {
439
- return & cobra.Command {
440
- Use : c .commandName ,
441
- Short : "Development kit for building Kubernetes extensions and tools." ,
442
- Long : fmt .Sprintf (`Development kit for building Kubernetes extensions and tools.
443
-
444
- Provides libraries and tools to create new projects, APIs and controllers.
445
- Includes tools for packaging artifacts into an installer container.
446
-
447
- Typical project lifecycle:
448
-
449
- - initialize a project:
450
-
451
- %[1]s init --domain example.com --license apache2 --owner "The Kubernetes authors"
426
+ // buildCmd creates the underlying cobra command and stores it internally.
427
+ func (c * cli ) buildCmd () error {
428
+ c .cmd = c .newRootCmd ()
452
429
453
- - create one or more a new resource APIs and add your code to them:
454
-
455
- %[1]s create api --group <group> --version <version> --kind <Kind>
456
-
457
- Create resource will prompt the user for if it should scaffold the Resource and / or Controller. To only
458
- scaffold a Controller for an existing Resource, select "n" for Resource. To only define
459
- the schema for a Resource without writing a Controller, select "n" for Controller.
460
-
461
- After the scaffold is written, api will run make on the project.
462
- ` ,
463
- c .commandName ),
464
- Example : fmt .Sprintf (`
465
- # Initialize your project
466
- %[1]s init --domain example.com --license apache2 --owner "The Kubernetes authors"
467
-
468
- # Create a frigates API with Group: ship, Version: v1beta1 and Kind: Frigate
469
- %[1]s create api --group ship --version v1beta1 --kind Frigate
430
+ // Get project version and plugin keys.
431
+ if err := c .getInfo (); err != nil {
432
+ return err
433
+ }
470
434
471
- # Edit the API Scheme
472
- nano api/v1beta1/frigate_types.go
435
+ // Resolve plugins for project version and plugin keys.
436
+ if err := c .resolve (); err != nil {
437
+ return err
438
+ }
473
439
474
- # Edit the Controller
475
- nano controllers/frigate_controller.go
440
+ // Add the subcommands
441
+ c . addSubcommands ()
476
442
477
- # Install CRDs into the Kubernetes cluster using kubectl apply
478
- make install
443
+ return nil
444
+ }
479
445
480
- # Regenerate code and run against the Kubernetes cluster configured by ~/.kube/config
481
- make run
482
- ` ,
483
- c .commandName ),
484
- Run : func (cmd * cobra.Command , args []string ) {
485
- if err := cmd .Help (); err != nil {
486
- log .Fatal (err )
446
+ // addExtraCommands adds the additional commands.
447
+ func (c * cli ) addExtraCommands () error {
448
+ for _ , cmd := range c .extraCommands {
449
+ for _ , subCmd := range c .cmd .Commands () {
450
+ if cmd .Name () == subCmd .Name () {
451
+ return fmt .Errorf ("command %q already exists" , cmd .Name ())
487
452
}
488
- },
453
+ }
454
+ c .cmd .AddCommand (cmd )
455
+ }
456
+ return nil
457
+ }
458
+
459
+ // printDeprecationWarnings prints the deprecation warnings of the resolved plugins.
460
+ func (c cli ) printDeprecationWarnings () {
461
+ for _ , p := range c .resolvedPlugins {
462
+ if d , isDeprecated := p .(plugin.Deprecated ); isDeprecated {
463
+ fmt .Printf (noticeColor , fmt .Sprintf (deprecationFmt , d .DeprecationWarning ()))
464
+ }
489
465
}
490
466
}
491
467
0 commit comments