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

kubernetes-sigs / kubebuilder / 25602897403

09 May 2026 01:58PM UTC coverage: 82.322% (+0.009%) from 82.313%
25602897403

Pull #5676

github

dongjiang1989
remove //nolint:gocost

Signed-off-by: dongjiang <dongjiang1989@126.com>
Pull Request #5676: ✨ (go/v4): upgrade golangci-lint to v2.12.1

10 of 14 new or added lines in 6 files covered. (71.43%)

72 existing lines in 4 files now uncovered.

7707 of 9362 relevant lines covered (82.32%)

73.69 hits per line

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

82.78
/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

45
// CLI is the command line utility that is used to scaffold kubebuilder project files.
46
type CLI struct {
47
        /* Fields set by Option */
48

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

70
        /* Internal fields */
71

72
        // Plugin keys to scaffold with.
73
        pluginKeys []string
74
        // Project version to scaffold.
75
        projectVersion config.Version
76

77
        // A filtered set of plugins that should be used by command constructors.
78
        resolvedPlugins []plugin.Plugin
79

80
        // Root command.
81
        cmd *cobra.Command
82

83
        // Underlying fs
84
        fs machinery.Filesystem
85
}
86

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

102
        // Build the cmd tree.
103
        if err := c.buildCmd(); err != nil {
11✔
104
                c.cmd.RunE = errCmdFunc(err)
1✔
105
                return c, nil
1✔
106
        }
1✔
107

108
        // Add extra commands injected by options.
109
        if err := c.addExtraCommands(); err != nil {
10✔
110
                return nil, err
1✔
111
        }
1✔
112

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

118
        // Write deprecation notices after all commands have been constructed.
119
        c.printDeprecationWarnings()
7✔
120

7✔
121
        return c, nil
7✔
122
}
123

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

41✔
137
        // Apply provided options.
41✔
138
        for _, option := range options {
93✔
139
                if err := option(c); err != nil {
69✔
140
                        return nil, err
17✔
141
                }
17✔
142
        }
143

144
        return c, nil
24✔
145
}
146

147
// buildCmd creates the underlying cobra command and stores it internally.
148
func (c *CLI) buildCmd() error {
12✔
149
        c.cmd = c.newRootCmd()
12✔
150

12✔
151
        var uve config.UnsupportedVersionError
12✔
152

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

172
        // Resolve plugins for project version and plugin keys.
173
        if err := c.resolvePlugins(); err != nil {
12✔
174
                return err
1✔
175
        }
1✔
176

177
        // Add the subcommands
178
        c.addSubcommands()
10✔
179

10✔
180
        return nil
10✔
181
}
182

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

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

198
        // Get project version and plugin info from flags
199
        if err := c.getInfoFromFlags(hasConfigFile); err != nil {
10✔
UNCOV
200
                return err
×
UNCOV
201
        }
×
202

203
        // Get project version and plugin info from defaults
204
        c.getInfoFromDefaults()
10✔
205

10✔
206
        return nil
10✔
207
}
208

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

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

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

230
        if err := cfg.Load(); err != nil {
24✔
231
                return fmt.Errorf("error loading configuration: %w", err)
12✔
232
        }
12✔
233

UNCOV
234
        return c.getInfoFromConfig(cfg.Config())
×
235
}
236

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

12✔
243
        for i := range args {
87✔
244
                arg := args[i]
75✔
245

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

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

272
        return false
12✔
273
}
274

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

×
NEW
292
        content, err := afero.ReadFile(fs, path)
×
NEW
293
        if err != nil {
×
294
                return nil
×
295
        }
×
296

297
        original := string(content)
×
298
        modified := original
×
299

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

311
        if modified != original {
×
312
                err := afero.WriteFile(fs, path, []byte(modified), machinery.DefaultFilePermission)
×
UNCOV
313
                if err != nil {
×
UNCOV
314
                        return fmt.Errorf("failed to write patched PROJECT file: %w", err)
×
315
                }
×
316
        }
317

318
        return nil
×
319
}
320

321
// getInfoFromConfig obtains the project version and plugin keys from the project config.
322
// It is extracted from getInfoFromConfigFile for testing purposes.
323
func (c *CLI) getInfoFromConfig(projectConfig config.Config) error {
3✔
324
        c.pluginKeys = projectConfig.GetPluginChain()
3✔
325
        c.projectVersion = projectConfig.GetVersion()
3✔
326

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

333
        return nil
2✔
334
}
335

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

350
        // Partially parse the command line arguments
351
        fs := pflag.NewFlagSet("base", pflag.ContinueOnError)
22✔
352

22✔
353
        // Load the base command global flags
22✔
354
        fs.AddFlagSet(c.cmd.PersistentFlags())
22✔
355

22✔
356
        // If we were unable to load the project configuration, we should also accept the project version flag
22✔
357
        var projectVersionStr string
22✔
358
        if !hasConfigFile {
44✔
359
                fs.StringVar(&projectVersionStr, projectVersionFlag, "", "project version")
22✔
360
        }
22✔
361

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

22✔
366
        // Omit unknown flags to avoid parsing errors
22✔
367
        fs.ParseErrorsAllowlist = pflag.ParseErrorsAllowlist{UnknownFlags: true}
22✔
368

22✔
369
        // Parse the arguments
22✔
370
        if err := fs.Parse(os.Args[1:]); err != nil {
22✔
UNCOV
371
                return fmt.Errorf("could not parse flags: %w", err)
×
UNCOV
372
        }
×
373

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

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

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

409
                c.pluginKeys = validPluginKeys
7✔
410
        }
411

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

420
        return nil
20✔
421
}
422

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

434
        // If the user provided a project version, use the default plugins for that project version
435
        if c.projectVersion.Validate() == nil {
13✔
436
                c.pluginKeys = c.defaultPlugins[c.projectVersion]
1✔
437
                return
1✔
438
        }
1✔
439

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

449
        // Else check if only default plugins for a project version were provided
450
        if len(c.defaultPlugins) == 1 {
16✔
451
                for projectVersion, defaultPlugins := range c.defaultPlugins {
14✔
452
                        c.pluginKeys = defaultPlugins
7✔
453
                        c.projectVersion = projectVersion
7✔
454
                        return
7✔
455
                }
7✔
456
        }
457
}
458

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

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

27✔
466
        for _, pluginKey := range c.pluginKeys {
55✔
467
                var extraErrMsg string
28✔
468

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

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

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

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

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

532
        return nil
16✔
533
}
534

535
// addSubcommands returns a root command with a subcommand tree reflecting the
536
// current project's state.
537
func (c *CLI) addSubcommands() {
10✔
538
        // add the alpha command if it has any subcommands enabled
10✔
539
        c.addAlphaCmd()
10✔
540

10✔
541
        // kubebuilder completion
10✔
542
        // Only add completion if requested
10✔
543
        if c.completionCommand {
11✔
544
                c.cmd.AddCommand(c.newCompletionCmd())
1✔
545
        }
1✔
546

547
        // kubebuilder create
548
        createCmd := c.newCreateCmd()
10✔
549
        // kubebuilder create api
10✔
550
        createCmd.AddCommand(c.newCreateAPICmd())
10✔
551
        createCmd.AddCommand(c.newCreateWebhookCmd())
10✔
552
        if createCmd.HasSubCommands() {
20✔
553
                c.cmd.AddCommand(createCmd)
10✔
554
        }
10✔
555

556
        // kubebuilder edit
557
        c.cmd.AddCommand(c.newEditCmd())
10✔
558

10✔
559
        // kubebuilder init
10✔
560
        c.cmd.AddCommand(c.newInitCmd())
10✔
561

10✔
562
        // kubebuilder version
10✔
563
        // Only add version if a version string was provided
10✔
564
        if c.version != "" {
11✔
565
                c.cmd.AddCommand(c.newVersionCmd())
1✔
566
        }
1✔
567
}
568

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

582
// printDeprecationWarnings prints the deprecation warnings of the resolved plugins.
583
func (c CLI) printDeprecationWarnings() {
7✔
584
        for _, p := range c.resolvedPlugins {
12✔
585
                if p != nil && p.(plugin.Deprecated) != nil && len(p.(plugin.Deprecated).DeprecationWarning()) > 0 {
6✔
586
                        _, _ = fmt.Fprintf(os.Stderr, noticeColor, fmt.Sprintf(deprecationFmt, p.(plugin.Deprecated).DeprecationWarning()))
1✔
587
                }
1✔
588
        }
589
}
590

591
// metadata returns CLI's metadata.
592
func (c CLI) metadata() plugin.CLIMetadata {
26✔
593
        return plugin.CLIMetadata{
26✔
594
                CommandName: c.commandName,
26✔
595
        }
26✔
596
}
26✔
597

598
// Run executes the CLI utility.
599
//
600
// If an error is found, command help and examples will be printed.
601
func (c CLI) Run() error {
1✔
602
        if err := c.cmd.Execute(); err != nil {
2✔
603
                // Don't return error if help was displayed (from --plugins --help pattern)
1✔
604
                if err == errHelpDisplayed {
1✔
UNCOV
605
                        return nil
×
UNCOV
606
                }
×
607
                return fmt.Errorf("error executing command: %w", err)
1✔
608
        }
609

610
        return nil
×
611
}
612

613
// Command returns the underlying root command.
614
func (c CLI) Command() *cobra.Command {
2✔
615
        return c.cmd
2✔
616
}
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