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

kubernetes-sigs / kubebuilder / 15372343500

01 Jun 2025 07:13AM UTC coverage: 71.52% (+0.1%) from 71.371%
15372343500

Pull #4844

github

camilamacedo86
(cli) fix: allow 'alpha generate' to work with unsupported plugins by patching PROJECT file in-memory

This change enables the 'kubebuilder alpha generate' command to work with projects that use old and unsupported plugin versions (e.g., go.kubebuilder.io/v2 or v3). It does so by loading the PROJECT config file into memory and replacing deprecated plugin keys with the latest supported version (v4) before parsing.

This prevents config loading errors and preserves support for CLI flags like --help and others.

Related to: https://github.com/kubernetes-sigs/kubebuilder/issues/4433
Pull Request #4844: 🐛 (cli) fix: allow 'alpha generate' to work with unsupported plugins by patching PROJECT file in-memory

33 of 71 new or added lines in 1 file covered. (46.48%)

7 existing lines in 1 file now uncovered.

2343 of 3276 relevant lines covered (71.52%)

16.55 hits per line

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

84.38
/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
        "os"
23
        "strings"
24

25
        log "github.com/sirupsen/logrus"
26

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

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

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

42
        pluginsFlag        = "plugins"
43
        projectVersionFlag = "project-version"
44
)
45

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

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

71
        /* Internal fields */
72

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

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

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

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

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

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

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

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

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

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

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

40✔
138
        // Apply provided options.
40✔
139
        for _, option := range options {
91✔
140
                if err := option(c); err != nil {
68✔
141
                        return nil, err
17✔
142
                }
17✔
143
        }
144

145
        return c, nil
23✔
146
}
147

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

12✔
152
        var uve config.UnsupportedVersionError
12✔
153

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

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

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

10✔
181
        return nil
10✔
182
}
183

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

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

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

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

10✔
207
        return nil
10✔
208
}
209

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

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

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

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

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

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

12✔
244
        for i := 0; i < len(args); i++ {
75✔
245
                arg := args[i]
63✔
246

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

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

273
        return false
12✔
274
}
275

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

×
NEW
288
        replacements := []pluginReplacement{
×
NEW
289
                {"go.kubebuilder.io/v2", "go.kubebuilder.io/v4"},
×
NEW
290
                {"go.kubebuilder.io/v3", "go.kubebuilder.io/v4"},
×
NEW
291
                {"go.kubebuilder.io/v3-alpha", "go.kubebuilder.io/v4"},
×
NEW
292
        }
×
NEW
293

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

NEW
299
        original := string(content)
×
NEW
300
        modified := original
×
NEW
301

×
NEW
302
        for _, rep := range replacements {
×
NEW
303
                if strings.Contains(modified, rep.Old) {
×
NEW
304
                        modified = strings.ReplaceAll(modified, rep.Old, rep.New)
×
NEW
305
                        log.Warnf("This project is using an old and no longer supported plugin layout %q. "+
×
NEW
306
                                "Replace in memory to %q to allow `alpha generate` to work.",
×
NEW
307
                                rep.Old, rep.New)
×
NEW
308
                }
×
309
        }
310

NEW
311
        if modified != original {
×
NEW
312
                return fmt.Errorf("failed to write patched PROJECT file: %w",
×
NEW
313
                        afero.WriteFile(fs, path, []byte(modified), 0o755))
×
NEW
314
        }
×
315

NEW
316
        return nil
×
317
}
318

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

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

331
        return nil
2✔
332
}
333

334
// getInfoFromFlags obtains the project version and plugin keys from flags.
335
func (c *CLI) getInfoFromFlags(hasConfigFile bool) error {
22✔
336
        // Partially parse the command line arguments
22✔
337
        fs := pflag.NewFlagSet("base", pflag.ContinueOnError)
22✔
338

22✔
339
        // Load the base command global flags
22✔
340
        fs.AddFlagSet(c.cmd.PersistentFlags())
22✔
341

22✔
342
        // If we were unable to load the project configuration, we should also accept the project version flag
22✔
343
        var projectVersionStr string
22✔
344
        if !hasConfigFile {
44✔
345
                fs.StringVar(&projectVersionStr, projectVersionFlag, "", "project version")
22✔
346
        }
22✔
347

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

22✔
352
        // Omit unknown flags to avoid parsing errors
22✔
353
        fs.ParseErrorsWhitelist = pflag.ParseErrorsWhitelist{UnknownFlags: true}
22✔
354

22✔
355
        // Parse the arguments
22✔
356
        if err := fs.Parse(os.Args[1:]); err != nil {
22✔
UNCOV
357
                return fmt.Errorf("could not parse flags: %w", err)
×
UNCOV
358
        }
×
359

360
        // If any plugin key was provided, replace those from the project configuration file
361
        if pluginKeys, err := fs.GetStringSlice(pluginsFlag); err != nil {
22✔
UNCOV
362
                return fmt.Errorf("invalid flag %q: %w", pluginsFlag, err)
×
363
        } else if len(pluginKeys) != 0 {
30✔
364
                // Remove leading and trailing spaces and validate the plugin keys
8✔
365
                for i, key := range pluginKeys {
24✔
366
                        pluginKeys[i] = strings.TrimSpace(key)
16✔
367
                        if err := plugin.ValidateKey(pluginKeys[i]); err != nil {
17✔
368
                                return fmt.Errorf("invalid plugin %q found in flags: %w", pluginKeys[i], err)
1✔
369
                        }
1✔
370
                }
371

372
                c.pluginKeys = pluginKeys
7✔
373
        }
374

375
        // If the project version flag was accepted but not provided keep the empty version and try to resolve it later,
376
        // else validate the provided project version
377
        if projectVersionStr != "" {
26✔
378
                if err := c.projectVersion.Parse(projectVersionStr); err != nil {
6✔
379
                        return fmt.Errorf("invalid project version flag: %w", err)
1✔
380
                }
1✔
381
        }
382

383
        return nil
20✔
384
}
385

386
// getInfoFromDefaults obtains the plugin keys, and maybe the project version from the default values
387
func (c *CLI) getInfoFromDefaults() {
14✔
388
        // Should not use default values if a plugin was already set
14✔
389
        // This checks includes the case where a project configuration file was found,
14✔
390
        // as it will always have at least one plugin key set by now
14✔
391
        if len(c.pluginKeys) != 0 {
16✔
392
                // We don't assign a default value for project version here because we may be able to
2✔
393
                // resolve the project version after resolving the plugins.
2✔
394
                return
2✔
395
        }
2✔
396

397
        // If the user provided a project version, use the default plugins for that project version
398
        if c.projectVersion.Validate() == nil {
13✔
399
                c.pluginKeys = c.defaultPlugins[c.projectVersion]
1✔
400
                return
1✔
401
        }
1✔
402

403
        // Else try to use the default plugins for the default project version
404
        if c.defaultProjectVersion.Validate() == nil {
13✔
405
                var found bool
2✔
406
                if c.pluginKeys, found = c.defaultPlugins[c.defaultProjectVersion]; found {
4✔
407
                        c.projectVersion = c.defaultProjectVersion
2✔
408
                        return
2✔
409
                }
2✔
410
        }
411

412
        // Else check if only default plugins for a project version were provided
413
        if len(c.defaultPlugins) == 1 {
16✔
414
                for projectVersion, defaultPlugins := range c.defaultPlugins {
14✔
415
                        c.pluginKeys = defaultPlugins
7✔
416
                        c.projectVersion = projectVersion
7✔
417
                        return
7✔
418
                }
7✔
419
        }
420
}
421

422
const unstablePluginMsg = " (plugin version is unstable, there may be an upgrade available: " +
423
        "https://kubebuilder.io/migration/plugin/plugins.html)"
424

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

27✔
429
        for _, pluginKey := range c.pluginKeys {
55✔
430
                var extraErrMsg string
28✔
431

28✔
432
                plugins := make([]plugin.Plugin, 0, len(c.plugins))
28✔
433
                for _, p := range c.plugins {
295✔
434
                        plugins = append(plugins, p)
267✔
435
                }
267✔
436
                // We can omit the error because plugin keys have already been validated
437
                plugins, _ = plugin.FilterPluginsByKey(plugins, pluginKey)
28✔
438
                if knownProjectVersion {
47✔
439
                        plugins = plugin.FilterPluginsByProjectVersion(plugins, c.projectVersion)
19✔
440
                        extraErrMsg += fmt.Sprintf(" for project version %q", c.projectVersion)
19✔
441
                }
19✔
442

443
                // Plugins are often released as "unstable" (alpha/beta) versions, then upgraded to "stable".
444
                // This upgrade effectively removes a plugin, which is fine because unstable plugins are
445
                // under no support contract. However users should be notified _why_ their plugin cannot be found.
446
                if _, version := plugin.SplitKey(pluginKey); version != "" {
42✔
447
                        var ver plugin.Version
14✔
448
                        if err := ver.Parse(version); err != nil {
14✔
UNCOV
449
                                return fmt.Errorf("error parsing input plugin version from key %q: %w", pluginKey, err)
×
UNCOV
450
                        }
×
451
                        if !ver.IsStable() {
14✔
452
                                extraErrMsg += unstablePluginMsg
×
UNCOV
453
                        }
×
454
                }
455

456
                // Only 1 plugin can match
457
                switch len(plugins) {
28✔
458
                case 1:
19✔
459
                        c.resolvedPlugins = append(c.resolvedPlugins, plugins[0])
19✔
460
                case 0:
6✔
461
                        return fmt.Errorf("no plugin could be resolved with key %q%s", pluginKey, extraErrMsg)
6✔
462
                default:
3✔
463
                        return fmt.Errorf("ambiguous plugin %q%s", pluginKey, extraErrMsg)
3✔
464
                }
465
        }
466

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

4✔
472
                // If there is only one common supported project version, resolve to it
4✔
473
        ProjectNumberVersionSwitch:
4✔
474
                switch len(supportedProjectVersions) {
4✔
475
                case 1:
1✔
476
                        c.projectVersion = supportedProjectVersions[0]
1✔
477
                case 0:
1✔
478
                        return fmt.Errorf("no project version supported by all the resolved plugins")
1✔
479
                default:
2✔
480
                        supportedProjectVersionStrings := make([]string, 0, len(supportedProjectVersions))
2✔
481
                        for _, supportedProjectVersion := range supportedProjectVersions {
6✔
482
                                // In case one of the multiple supported versions is the default one, choose that and exit the switch
4✔
483
                                if supportedProjectVersion.Compare(c.defaultProjectVersion) == 0 {
5✔
484
                                        c.projectVersion = c.defaultProjectVersion
1✔
485
                                        break ProjectNumberVersionSwitch
1✔
486
                                }
487
                                supportedProjectVersionStrings = append(supportedProjectVersionStrings,
3✔
488
                                        fmt.Sprintf("%q", supportedProjectVersion))
3✔
489
                        }
490
                        return fmt.Errorf("ambiguous project version, resolved plugins support the following project versions: %s",
1✔
491
                                strings.Join(supportedProjectVersionStrings, ", "))
1✔
492
                }
493
        }
494

495
        return nil
16✔
496
}
497

498
// addSubcommands returns a root command with a subcommand tree reflecting the
499
// current project's state.
500
func (c *CLI) addSubcommands() {
10✔
501
        // add the alpha command if it has any subcommands enabled
10✔
502
        c.addAlphaCmd()
10✔
503

10✔
504
        // kubebuilder completion
10✔
505
        // Only add completion if requested
10✔
506
        if c.completionCommand {
11✔
507
                c.cmd.AddCommand(c.newCompletionCmd())
1✔
508
        }
1✔
509

510
        // kubebuilder create
511
        createCmd := c.newCreateCmd()
10✔
512
        // kubebuilder create api
10✔
513
        createCmd.AddCommand(c.newCreateAPICmd())
10✔
514
        createCmd.AddCommand(c.newCreateWebhookCmd())
10✔
515
        if createCmd.HasSubCommands() {
20✔
516
                c.cmd.AddCommand(createCmd)
10✔
517
        }
10✔
518

519
        // kubebuilder edit
520
        c.cmd.AddCommand(c.newEditCmd())
10✔
521

10✔
522
        // kubebuilder init
10✔
523
        c.cmd.AddCommand(c.newInitCmd())
10✔
524

10✔
525
        // kubebuilder version
10✔
526
        // Only add version if a version string was provided
10✔
527
        if c.version != "" {
11✔
528
                c.cmd.AddCommand(c.newVersionCmd())
1✔
529
        }
1✔
530
}
531

532
// addExtraCommands adds the additional commands.
533
func (c *CLI) addExtraCommands() error {
9✔
534
        for _, cmd := range c.extraCommands {
11✔
535
                for _, subCmd := range c.cmd.Commands() {
10✔
536
                        if cmd.Name() == subCmd.Name() {
9✔
537
                                return fmt.Errorf("command %q already exists", cmd.Name())
1✔
538
                        }
1✔
539
                }
540
                c.cmd.AddCommand(cmd)
1✔
541
        }
542
        return nil
8✔
543
}
544

545
// printDeprecationWarnings prints the deprecation warnings of the resolved plugins.
546
func (c CLI) printDeprecationWarnings() {
7✔
547
        for _, p := range c.resolvedPlugins {
12✔
548
                if p != nil && p.(plugin.Deprecated) != nil && len(p.(plugin.Deprecated).DeprecationWarning()) > 0 {
6✔
549
                        _, _ = fmt.Fprintf(os.Stderr, noticeColor, fmt.Sprintf(deprecationFmt, p.(plugin.Deprecated).DeprecationWarning()))
1✔
550
                }
1✔
551
        }
552
}
553

554
// metadata returns CLI's metadata.
555
func (c CLI) metadata() plugin.CLIMetadata {
24✔
556
        return plugin.CLIMetadata{
24✔
557
                CommandName: c.commandName,
24✔
558
        }
24✔
559
}
24✔
560

561
// Run executes the CLI utility.
562
//
563
// If an error is found, command help and examples will be printed.
564
func (c CLI) Run() error {
1✔
565
        if err := c.cmd.Execute(); err != nil {
2✔
566
                return fmt.Errorf("error executing command: %w", err)
1✔
567
        }
1✔
568

UNCOV
569
        return nil
×
570
}
571

572
// Command returns the underlying root command.
573
func (c CLI) Command() *cobra.Command {
2✔
574
        return c.cmd
2✔
575
}
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