• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubernetes-sigs / kubebuilder / 26977941509

04 Jun 2026 08:34PM UTC coverage: 82.459%. Remained the same
26977941509

Pull #5711

github

camilamacedo86
fix(docs): satisfy goconst in book and testdata samples
Pull Request #5711: ✨(go/v4) Upgrade golang-ci from v2.11.4 to v2.12.2

12 of 16 new or added lines in 7 files covered. (75.0%)

7799 of 9458 relevant lines covered (82.46%)

81.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.37
/pkg/cli/cli.go
1
/*
2
Copyright 2020 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 cli
18

19
import (
20
        "errors"
21
        "fmt"
22
        log "log/slog"
23
        "os"
24
        "strings"
25

26
        "github.com/spf13/afero"
27
        "github.com/spf13/cobra"
28
        "github.com/spf13/pflag"
29

30
        "sigs.k8s.io/kubebuilder/v4/pkg/config"
31
        yamlstore "sigs.k8s.io/kubebuilder/v4/pkg/config/store/yaml"
32
        "sigs.k8s.io/kubebuilder/v4/pkg/machinery"
33
        "sigs.k8s.io/kubebuilder/v4/pkg/model/stage"
34
        "sigs.k8s.io/kubebuilder/v4/pkg/plugin"
35
)
36

37
const (
38
        noticeColor    = "\033[1;33m%s\033[0m"
39
        deprecationFmt = "[Deprecation Notice] %s\n\n"
40

41
        pluginsFlag        = "plugins"
42
        projectVersionFlag = "project-version"
43

44
        kubebuilderCommandName = "kubebuilder"
45
        pluginGoKubebuilderV4  = "go.kubebuilder.io/v4"
46
        pluginGoKubebuilderV2  = "go.kubebuilder.io/v2"
47
)
48

49
// CLI is the command line utility that is used to scaffold kubebuilder project files.
50
type CLI struct {
51
        /* Fields set by Option */
52

53
        // Root command name. It is injected downstream to provide correct help, usage, examples and errors.
54
        commandName string
55
        // Full CLI version string.
56
        version string
57
        // CLI version string (just the CLI version number, no extra information).
58
        cliVersion string
59
        // CLI root's command description.
60
        description string
61
        // Plugins registered in the CLI.
62
        plugins map[string]plugin.Plugin
63
        // Default plugins in case none is provided and a config file can't be found.
64
        defaultPlugins map[config.Version][]string
65
        // Default project version in case none is provided and a config file can't be found.
66
        defaultProjectVersion config.Version
67
        // Commands injected by options.
68
        extraCommands []*cobra.Command
69
        // Alpha commands injected by options.
70
        extraAlphaCommands []*cobra.Command
71
        // Whether to add a completion command to the CLI.
72
        completionCommand bool
73

74
        /* Internal fields */
75

76
        // Plugin keys to scaffold with.
77
        pluginKeys []string
78
        // Project version to scaffold.
79
        projectVersion config.Version
80

81
        // A filtered set of plugins that should be used by command constructors.
82
        resolvedPlugins []plugin.Plugin
83

84
        // Root command.
85
        cmd *cobra.Command
86

87
        // Underlying fs
88
        fs machinery.Filesystem
89
}
90

91
// New creates a new CLI instance.
92
//
93
// It follows the functional options pattern in order to customize the resulting CLI.
94
//
95
// It returns an error if any of the provided options fails. As some processing needs
96
// to be done, execution errors may be found here. Instead of returning an error, this
97
// function will return a valid CLI that errors in Run so that help is provided to the
98
// user.
99
func New(options ...Option) (*CLI, error) {
11✔
100
        // Create the CLI.
11✔
101
        c, err := newCLI(options...)
11✔
102
        if err != nil {
12✔
103
                return nil, err
1✔
104
        }
1✔
105

106
        // Build the cmd tree.
107
        if err := c.buildCmd(); err != nil {
11✔
108
                c.cmd.RunE = errCmdFunc(err)
1✔
109
                return c, nil
1✔
110
        }
1✔
111

112
        // Add extra commands injected by options.
113
        if err := c.addExtraCommands(); err != nil {
10✔
114
                return nil, err
1✔
115
        }
1✔
116

117
        // Add extra alpha commands injected by options.
118
        if err := c.addExtraAlphaCommands(); err != nil {
9✔
119
                return nil, err
1✔
120
        }
1✔
121

122
        // Write deprecation notices after all commands have been constructed.
123
        c.printDeprecationWarnings()
7✔
124

7✔
125
        return c, nil
7✔
126
}
127

128
// newCLI creates a default CLI instance and applies the provided options.
129
// It is as a separate function for test purposes.
130
func newCLI(options ...Option) (*CLI, error) {
41✔
131
        // Default CLI options.
41✔
132
        c := &CLI{
41✔
133
                commandName: kubebuilderCommandName,
41✔
134
                description: `CLI tool for building Kubernetes extensions and tools.
41✔
135
`,
41✔
136
                plugins:        make(map[string]plugin.Plugin),
41✔
137
                defaultPlugins: make(map[config.Version][]string),
41✔
138
                fs:             machinery.Filesystem{FS: afero.NewOsFs()},
41✔
139
        }
41✔
140

41✔
141
        // Apply provided options.
41✔
142
        for _, option := range options {
93✔
143
                if err := option(c); err != nil {
69✔
144
                        return nil, err
17✔
145
                }
17✔
146
        }
147

148
        return c, nil
24✔
149
}
150

151
// buildCmd creates the underlying cobra command and stores it internally.
152
func (c *CLI) buildCmd() error {
12✔
153
        c.cmd = c.newRootCmd()
12✔
154

12✔
155
        var uve config.UnsupportedVersionError
12✔
156

12✔
157
        // Get project version and plugin keys.
12✔
158
        switch err := c.getInfo(); {
12✔
159
        case err == nil:
10✔
160
        case errors.As(err, &uve) && uve.Version.Compare(config.Version{Number: 3, Stage: stage.Alpha}) == 0:
1✔
161
                // Check if the corresponding stable version exists, set c.projectVersion and break
1✔
162
                stableVersion := config.Version{
1✔
163
                        Number: uve.Version.Number,
1✔
164
                }
1✔
165
                if config.IsRegistered(stableVersion) {
2✔
166
                        // Use the stableVersion
1✔
167
                        c.projectVersion = stableVersion
1✔
168
                } else {
1✔
169
                        // stable version not registered, let's bail out
×
170
                        return err
×
171
                }
×
172
        default:
1✔
173
                return err
1✔
174
        }
175

176
        // Resolve plugins for project version and plugin keys.
177
        if err := c.resolvePlugins(); err != nil {
12✔
178
                return err
1✔
179
        }
1✔
180

181
        // Add the subcommands
182
        c.addSubcommands()
10✔
183

10✔
184
        return nil
10✔
185
}
186

187
// getInfo obtains the plugin keys and project version resolving conflicts between the project config file and flags.
188
func (c *CLI) getInfo() error {
12✔
189
        // Get plugin keys and project version from project configuration file
12✔
190
        // We discard the error if file doesn't exist because not being able to read a project configuration
12✔
191
        // file is not fatal for some commands. The ones that require it need to check its existence later.
12✔
192
        hasConfigFile := true
12✔
193
        if err := c.getInfoFromConfigFile(); errors.Is(err, os.ErrNotExist) {
22✔
194
                hasConfigFile = false
10✔
195
        } else if err != nil {
14✔
196
                return err
2✔
197
        }
2✔
198

199
        // We can't early return here in case a project configuration file was found because
200
        // this command call may override the project plugins.
201

202
        // Get project version and plugin info from flags
203
        if err := c.getInfoFromFlags(hasConfigFile); err != nil {
10✔
204
                return err
×
205
        }
×
206

207
        // Get project version and plugin info from defaults
208
        c.getInfoFromDefaults()
10✔
209

10✔
210
        return nil
10✔
211
}
212

213
// getInfoFromConfigFile obtains the project version and plugin keys from the project config file.
214
func (c *CLI) getInfoFromConfigFile() error {
12✔
215
        // Read the project configuration file
12✔
216
        cfg := yamlstore.New(c.fs)
12✔
217

12✔
218
        // Workaround for https://github.com/kubernetes-sigs/kubebuilder/issues/4433
12✔
219
        //
12✔
220
        // This allows the `kubebuilder alpha generate` command to work with old projects
12✔
221
        // that use plugin versions no longer supported (like go.kubebuilder.io/v3).
12✔
222
        //
12✔
223
        // We read the PROJECT file into memory and update the plugin version (e.g. from v3 to v4)
12✔
224
        // before the CLI tries to load it. This avoids errors during config loading
12✔
225
        // and lets users migrate their project layout from go/v3 to go/v4.
12✔
226

12✔
227
        if isAlphaGenerateCommand(os.Args[1:]) {
12✔
228
                // Patch raw file bytes before unmarshalling
×
229
                if err := patchProjectFileInMemoryIfNeeded(c.fs.FS, yamlstore.DefaultPath); err != nil {
×
230
                        return err
×
231
                }
×
232
        }
233

234
        if err := cfg.Load(); err != nil {
24✔
235
                return fmt.Errorf("error loading configuration: %w", err)
12✔
236
        }
12✔
237

238
        return c.getInfoFromConfig(cfg.Config())
×
239
}
240

241
// isAlphaGenerateCommand checks if the command invocation is `kubebuilder alpha generate`
242
// by scanning os.Args (excluding global flags). It returns true if "alpha" is followed by "generate".
243
func isAlphaGenerateCommand(args []string) bool {
12✔
244
        positional := []string{}
12✔
245
        skip := false
12✔
246

12✔
247
        for i := range args {
87✔
248
                arg := args[i]
75✔
249

75✔
250
                // Skip flags and their values
75✔
251
                if strings.HasPrefix(arg, "-") {
148✔
252
                        // If the flag is in --flag=value format, skip only this one
73✔
253
                        if strings.Contains(arg, "=") {
133✔
254
                                continue
60✔
255
                        }
256
                        // If it's --flag value format, skip next one too
257
                        if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") {
14✔
258
                                skip = true
1✔
259
                        }
1✔
260
                        continue
13✔
261
                }
262
                if skip {
3✔
263
                        skip = false
1✔
264
                        continue
1✔
265
                }
266
                positional = append(positional, arg)
1✔
267
        }
268

269
        // Check for `alpha generate` in positional arguments
270
        for i := 0; i < len(positional)-1; i++ {
12✔
271
                if positional[i] == "alpha" && positional[i+1] == "generate" {
×
272
                        return true
×
273
                }
×
274
        }
275

276
        return false
12✔
277
}
278

279
// patchProjectFileInMemoryIfNeeded updates deprecated plugin keys in the PROJECT file in place,
280
// so that users can run `kubebuilder alpha generate` even with older plugin layouts.
281
//
282
// See: https://github.com/kubernetes-sigs/kubebuilder/issues/4433
283
//
284
// This ensures the CLI can successfully load the config without failing on unsupported plugin versions.
285
func patchProjectFileInMemoryIfNeeded(fs afero.Fs, path string) error {
×
286
        type pluginReplacement struct {
×
287
                Old string
×
288
                New string
×
289
        }
×
290

×
291
        replacements := []pluginReplacement{
×
NEW
292
                {pluginGoKubebuilderV2, pluginGoKubebuilderV4},
×
NEW
293
                {"go.kubebuilder.io/v3", pluginGoKubebuilderV4},
×
NEW
294
                {"go.kubebuilder.io/v3-alpha", pluginGoKubebuilderV4},
×
295
        }
×
296

×
297
        content, err := afero.ReadFile(fs, path)
×
298
        if err != nil {
×
299
                return nil
×
300
        }
×
301

302
        original := string(content)
×
303
        modified := original
×
304

×
305
        for _, rep := range replacements {
×
306
                if strings.Contains(modified, rep.Old) {
×
307
                        modified = strings.ReplaceAll(modified, rep.Old, rep.New)
×
308
                        log.Warn("Project is using an old and unsupported plugin layout",
×
309
                                "old_layout", rep.Old,
×
310
                                "new_layout", rep.New,
×
311
                                "note", "Replace in memory to allow `alpha generate` to work.",
×
312
                        )
×
313
                }
×
314
        }
315

316
        if modified != original {
×
317
                err := afero.WriteFile(fs, path, []byte(modified), machinery.DefaultFilePermission)
×
318
                if err != nil {
×
319
                        return fmt.Errorf("failed to write patched PROJECT file: %w", err)
×
320
                }
×
321
        }
322

323
        return nil
×
324
}
325

326
// getInfoFromConfig obtains the project version and plugin keys from the project config.
327
// It is extracted from getInfoFromConfigFile for testing purposes.
328
func (c *CLI) getInfoFromConfig(projectConfig config.Config) error {
3✔
329
        c.pluginKeys = projectConfig.GetPluginChain()
3✔
330
        c.projectVersion = projectConfig.GetVersion()
3✔
331

3✔
332
        for _, pluginKey := range c.pluginKeys {
7✔
333
                if err := plugin.ValidateKey(pluginKey); err != nil {
5✔
334
                        return fmt.Errorf("invalid plugin key found in project configuration file: %w", err)
1✔
335
                }
1✔
336
        }
337

338
        return nil
2✔
339
}
340

341
// getInfoFromFlags obtains the project version and plugin keys from flags.
342
func (c *CLI) getInfoFromFlags(hasConfigFile bool) error {
24✔
343
        // Check if --plugins is followed by --help or -h to avoid parsing help as a plugin value
24✔
344
        // This fixes: kubebuilder init --plugins --help
24✔
345
        for i := 0; i < len(os.Args)-1; i++ {
218✔
346
                if os.Args[i] == "--plugins" || os.Args[i] == "--plugins=" {
204✔
347
                        nextArg := os.Args[i+1]
10✔
348
                        if isHelpFlag(nextArg) {
12✔
349
                                // Help was requested, return early to let Cobra handle it
2✔
350
                                return nil
2✔
351
                        }
2✔
352
                }
353
        }
354

355
        // Partially parse the command line arguments
356
        fs := pflag.NewFlagSet("base", pflag.ContinueOnError)
22✔
357

22✔
358
        // Load the base command global flags
22✔
359
        fs.AddFlagSet(c.cmd.PersistentFlags())
22✔
360

22✔
361
        // If we were unable to load the project configuration, we should also accept the project version flag
22✔
362
        var projectVersionStr string
22✔
363
        if !hasConfigFile {
44✔
364
                fs.StringVar(&projectVersionStr, projectVersionFlag, "", "project version")
22✔
365
        }
22✔
366

367
        // FlagSet special cases --help and -h, so we need to create a dummy flag with these 2 values to prevent the default
368
        // behavior (printing the usage of this FlagSet) as we want to print the usage message of the underlying command.
369
        fs.BoolP("help", "h", false, fmt.Sprintf("help for %s", c.commandName))
22✔
370

22✔
371
        // Omit unknown flags to avoid parsing errors
22✔
372
        fs.ParseErrorsAllowlist = pflag.ParseErrorsAllowlist{UnknownFlags: true}
22✔
373

22✔
374
        // Parse the arguments
22✔
375
        if err := fs.Parse(os.Args[1:]); err != nil {
22✔
376
                return fmt.Errorf("could not parse flags: %w", err)
×
377
        }
×
378

379
        // If any plugin key was provided, replace those from the project configuration file
380
        if pluginKeys, err := fs.GetStringSlice(pluginsFlag); err != nil {
22✔
381
                return fmt.Errorf("invalid flag %q: %w", pluginsFlag, err)
×
382
        } else if len(pluginKeys) != 0 {
30✔
383
                // Filter out help flags that may have been incorrectly parsed as plugin values
8✔
384
                // This fixes the issue where "kubebuilder edit --plugins --help" treats --help as a plugin
8✔
385
                validPluginKeys := make([]string, 0, len(pluginKeys))
8✔
386
                helpRequested := false
8✔
387
                for _, key := range pluginKeys {
24✔
388
                        key = strings.TrimSpace(key)
16✔
389
                        // Skip help flags
16✔
390
                        if isHelpFlag(key) {
16✔
391
                                helpRequested = true
×
392
                                continue
×
393
                        }
394
                        validPluginKeys = append(validPluginKeys, key)
16✔
395
                }
396

397
                // If help was requested via --plugins flag, set the help flag to trigger Cobra's help display
398
                // This prevents command execution and shows help instead
399
                if helpRequested {
8✔
400
                        if err := fs.Set("help", "true"); err == nil {
×
401
                                return nil
×
402
                        }
×
403
                        // If setting help flag fails, still return nil to avoid validation errors
404
                        return nil
×
405
                }
406

407
                // Validate the remaining plugin keys
408
                for i, key := range validPluginKeys {
24✔
409
                        if err := plugin.ValidateKey(key); err != nil {
17✔
410
                                return fmt.Errorf("invalid plugin %q found in flags: %w", validPluginKeys[i], err)
1✔
411
                        }
1✔
412
                }
413

414
                c.pluginKeys = validPluginKeys
7✔
415
        }
416

417
        // If the project version flag was accepted but not provided keep the empty version and try to resolve it later,
418
        // else validate the provided project version
419
        if projectVersionStr != "" {
26✔
420
                if err := c.projectVersion.Parse(projectVersionStr); err != nil {
6✔
421
                        return fmt.Errorf("invalid project version flag: %w", err)
1✔
422
                }
1✔
423
        }
424

425
        return nil
20✔
426
}
427

428
// getInfoFromDefaults obtains the plugin keys, and maybe the project version from the default values
429
func (c *CLI) getInfoFromDefaults() {
14✔
430
        // Should not use default values if a plugin was already set
14✔
431
        // This checks includes the case where a project configuration file was found,
14✔
432
        // as it will always have at least one plugin key set by now
14✔
433
        if len(c.pluginKeys) != 0 {
16✔
434
                // We don't assign a default value for project version here because we may be able to
2✔
435
                // resolve the project version after resolving the plugins.
2✔
436
                return
2✔
437
        }
2✔
438

439
        // If the user provided a project version, use the default plugins for that project version
440
        if c.projectVersion.Validate() == nil {
13✔
441
                c.pluginKeys = c.defaultPlugins[c.projectVersion]
1✔
442
                return
1✔
443
        }
1✔
444

445
        // Else try to use the default plugins for the default project version
446
        if c.defaultProjectVersion.Validate() == nil {
13✔
447
                var found bool
2✔
448
                if c.pluginKeys, found = c.defaultPlugins[c.defaultProjectVersion]; found {
4✔
449
                        c.projectVersion = c.defaultProjectVersion
2✔
450
                        return
2✔
451
                }
2✔
452
        }
453

454
        // Else check if only default plugins for a project version were provided
455
        if len(c.defaultPlugins) == 1 {
16✔
456
                for projectVersion, defaultPlugins := range c.defaultPlugins {
14✔
457
                        c.pluginKeys = defaultPlugins
7✔
458
                        c.projectVersion = projectVersion
7✔
459
                        return
7✔
460
                }
7✔
461
        }
462
}
463

464
const unstablePluginMsg = " (plugin version is unstable, there may be an upgrade available: " +
465
        "https://kubebuilder.io/plugins/plugins-versioning)"
466

467
// resolvePlugins selects from the available plugins those that match the project version and plugin keys provided.
468
func (c *CLI) resolvePlugins() error {
27✔
469
        knownProjectVersion := c.projectVersion.Validate() == nil
27✔
470

27✔
471
        for _, pluginKey := range c.pluginKeys {
55✔
472
                var extraErrMsg string
28✔
473

28✔
474
                plugins := make([]plugin.Plugin, 0, len(c.plugins))
28✔
475
                for _, p := range c.plugins {
295✔
476
                        plugins = append(plugins, p)
267✔
477
                }
267✔
478
                // We can omit the error because plugin keys have already been validated
479
                plugins, _ = plugin.FilterPluginsByKey(plugins, pluginKey)
28✔
480
                if knownProjectVersion {
47✔
481
                        plugins = plugin.FilterPluginsByProjectVersion(plugins, c.projectVersion)
19✔
482
                        extraErrMsg += fmt.Sprintf(" for project version %q", c.projectVersion)
19✔
483
                }
19✔
484

485
                // Plugins are often released as "unstable" (alpha/beta) versions, then upgraded to "stable".
486
                // This upgrade effectively removes a plugin, which is fine because unstable plugins are
487
                // under no support contract. However users should be notified _why_ their plugin cannot be found.
488
                if _, version := plugin.SplitKey(pluginKey); version != "" {
42✔
489
                        var ver plugin.Version
14✔
490
                        if err := ver.Parse(version); err != nil {
14✔
491
                                return fmt.Errorf("error parsing input plugin version from key %q: %w", pluginKey, err)
×
492
                        }
×
493
                        if !ver.IsStable() {
14✔
494
                                extraErrMsg += unstablePluginMsg
×
495
                        }
×
496
                }
497

498
                // Only 1 plugin can match
499
                switch len(plugins) {
28✔
500
                case 1:
19✔
501
                        c.resolvedPlugins = append(c.resolvedPlugins, plugins[0])
19✔
502
                case 0:
6✔
503
                        return fmt.Errorf("no plugin could be resolved with key %q%s", pluginKey, extraErrMsg)
6✔
504
                default:
3✔
505
                        return fmt.Errorf("ambiguous plugin %q%s", pluginKey, extraErrMsg)
3✔
506
                }
507
        }
508

509
        // Now we can try to resolve the project version if not known by this point
510
        if !knownProjectVersion && len(c.resolvedPlugins) > 0 {
22✔
511
                // Extract the common supported project versions
4✔
512
                supportedProjectVersions := plugin.CommonSupportedProjectVersions(c.resolvedPlugins...)
4✔
513

4✔
514
                // If there is only one common supported project version, resolve to it
4✔
515
        ProjectNumberVersionSwitch:
4✔
516
                switch len(supportedProjectVersions) {
4✔
517
                case 1:
1✔
518
                        c.projectVersion = supportedProjectVersions[0]
1✔
519
                case 0:
1✔
520
                        return fmt.Errorf("no project version supported by all the resolved plugins")
1✔
521
                default:
2✔
522
                        supportedProjectVersionStrings := make([]string, 0, len(supportedProjectVersions))
2✔
523
                        for _, supportedProjectVersion := range supportedProjectVersions {
6✔
524
                                // In case one of the multiple supported versions is the default one, choose that and exit the switch
4✔
525
                                if supportedProjectVersion.Compare(c.defaultProjectVersion) == 0 {
5✔
526
                                        c.projectVersion = c.defaultProjectVersion
1✔
527
                                        break ProjectNumberVersionSwitch
1✔
528
                                }
529
                                supportedProjectVersionStrings = append(supportedProjectVersionStrings,
3✔
530
                                        fmt.Sprintf("%q", supportedProjectVersion))
3✔
531
                        }
532
                        return fmt.Errorf("ambiguous project version, resolved plugins support the following project versions: %s",
1✔
533
                                strings.Join(supportedProjectVersionStrings, ", "))
1✔
534
                }
535
        }
536

537
        return nil
16✔
538
}
539

540
// addSubcommands returns a root command with a subcommand tree reflecting the
541
// current project's state.
542
func (c *CLI) addSubcommands() {
10✔
543
        // add the alpha command if it has any subcommands enabled
10✔
544
        c.addAlphaCmd()
10✔
545

10✔
546
        // kubebuilder completion
10✔
547
        // Only add completion if requested
10✔
548
        if c.completionCommand {
11✔
549
                c.cmd.AddCommand(c.newCompletionCmd())
1✔
550
        }
1✔
551

552
        // kubebuilder create
553
        createCmd := c.newCreateCmd()
10✔
554
        // kubebuilder create api
10✔
555
        createCmd.AddCommand(c.newCreateAPICmd())
10✔
556
        createCmd.AddCommand(c.newCreateWebhookCmd())
10✔
557
        if createCmd.HasSubCommands() {
20✔
558
                c.cmd.AddCommand(createCmd)
10✔
559
        }
10✔
560

561
        // kubebuilder edit
562
        c.cmd.AddCommand(c.newEditCmd())
10✔
563

10✔
564
        // kubebuilder init
10✔
565
        c.cmd.AddCommand(c.newInitCmd())
10✔
566

10✔
567
        // kubebuilder version
10✔
568
        // Only add version if a version string was provided
10✔
569
        if c.version != "" {
11✔
570
                c.cmd.AddCommand(c.newVersionCmd())
1✔
571
        }
1✔
572
}
573

574
// addExtraCommands adds the additional commands.
575
func (c *CLI) addExtraCommands() error {
9✔
576
        for _, cmd := range c.extraCommands {
11✔
577
                for _, subCmd := range c.cmd.Commands() {
10✔
578
                        if cmd.Name() == subCmd.Name() {
9✔
579
                                return fmt.Errorf("command %q already exists", cmd.Name())
1✔
580
                        }
1✔
581
                }
582
                c.cmd.AddCommand(cmd)
1✔
583
        }
584
        return nil
8✔
585
}
586

587
// printDeprecationWarnings prints the deprecation warnings of the resolved plugins.
588
func (c CLI) printDeprecationWarnings() {
7✔
589
        for _, p := range c.resolvedPlugins {
12✔
590
                if p == nil {
5✔
591
                        continue
×
592
                }
593
                if deprecated, ok := p.(plugin.Deprecated); ok && len(deprecated.DeprecationWarning()) > 0 {
6✔
594
                        _, _ = fmt.Fprintf(os.Stderr, noticeColor, fmt.Sprintf(deprecationFmt, deprecated.DeprecationWarning()))
1✔
595
                }
1✔
596
        }
597
}
598

599
// metadata returns CLI's metadata.
600
func (c CLI) metadata() plugin.CLIMetadata {
26✔
601
        return plugin.CLIMetadata{
26✔
602
                CommandName: c.commandName,
26✔
603
        }
26✔
604
}
26✔
605

606
// Run executes the CLI utility.
607
//
608
// If an error is found, command help and examples will be printed.
609
func (c CLI) Run() error {
1✔
610
        if err := c.cmd.Execute(); err != nil {
2✔
611
                // Don't return error if help was displayed (from --plugins --help pattern)
1✔
612
                if err == errHelpDisplayed {
1✔
613
                        return nil
×
614
                }
×
615
                return fmt.Errorf("error executing command: %w", err)
1✔
616
        }
617

618
        return nil
×
619
}
620

621
// Command returns the underlying root command.
622
func (c CLI) Command() *cobra.Command {
2✔
623
        return c.cmd
2✔
624
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc