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

kubernetes-sigs / kubebuilder / 14309075193

07 Apr 2025 12:23PM UTC coverage: 71.57%. First build
14309075193

Pull #4752

github

dongjiang1989
update golangci-lint v2

Signed-off-by: dongjiang <dongjiang1989@126.com>

revert by codereview and make generate

Signed-off-by: dongjiang <dongjiang1989@126.com>
Pull Request #4752: ✨Update golangci-lint to v2.0.2 and adjust configuration

3 of 5 new or added lines in 3 files covered. (60.0%)

2311 of 3229 relevant lines covered (71.57%)

16.53 hits per line

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

40.28
/pkg/cli/cmd_helpers.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
        "github.com/spf13/cobra"
27

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

36
// noResolvedPluginError is returned by subcommands that require a plugin when none was resolved.
37
type noResolvedPluginError struct{}
38

39
// Error implements error interface.
40
func (e noResolvedPluginError) Error() string {
12✔
41
        return "no resolved plugin, please verify the project version and plugins specified in flags or configuration file"
12✔
42
}
12✔
43

44
// noAvailablePluginError is returned by subcommands that require a plugin when none of their specific type was found.
45
type noAvailablePluginError struct {
46
        subcommand string
47
}
48

49
// Error implements error interface.
50
func (e noAvailablePluginError) Error() string {
4✔
51
        return fmt.Sprintf("resolved plugins do not provide any %s subcommand", e.subcommand)
4✔
52
}
4✔
53

54
// cmdErr updates a cobra command to output error information when executed
55
// or used with the help flag.
56
func cmdErr(cmd *cobra.Command, err error) {
16✔
57
        cmd.Long = fmt.Sprintf("%s\nNote: %v", cmd.Long, err)
16✔
58
        cmd.RunE = errCmdFunc(err)
16✔
59
}
16✔
60

61
// errCmdFunc returns a cobra RunE function that returns the provided error
62
func errCmdFunc(err error) func(*cobra.Command, []string) error {
47✔
63
        return func(*cobra.Command, []string) error {
48✔
64
                return err
1✔
65
        }
1✔
66
}
67

68
// keySubcommandTuple represents a pairing of the key of a plugin with a plugin.Subcommand.
69
type keySubcommandTuple struct {
70
        key        string
71
        subcommand plugin.Subcommand
72

73
        // skip will be used to flag subcommands that should be skipped after any hook returned a plugin.ExitError.
74
        skip bool
75
}
76

77
// filterSubcommands returns a list of plugin keys and subcommands from a filtered list of resolved plugins.
78
func (c *CLI) filterSubcommands(
79
        filter func(plugin.Plugin) bool,
80
        extract func(plugin.Plugin) plugin.Subcommand,
81
) []keySubcommandTuple {
28✔
82
        // Unbundle plugins
28✔
83
        plugins := make([]plugin.Plugin, 0, len(c.resolvedPlugins))
28✔
84
        for _, p := range c.resolvedPlugins {
56✔
85
                if bundle, isBundle := p.(plugin.Bundle); isBundle {
28✔
86
                        plugins = append(plugins, bundle.Plugins()...)
×
87
                } else {
28✔
88
                        plugins = append(plugins, p)
28✔
89
                }
28✔
90
        }
91

92
        tuples := make([]keySubcommandTuple, 0, len(plugins))
28✔
93
        for _, p := range plugins {
56✔
94
                if filter(p) {
52✔
95
                        tuples = append(tuples, keySubcommandTuple{
24✔
96
                                key:        plugin.KeyFor(p),
24✔
97
                                subcommand: extract(p),
24✔
98
                        })
24✔
99
                }
24✔
100
        }
101
        return tuples
28✔
102
}
103

104
// applySubcommandHooks runs the initialization hooks and configures the commands pre-run,
105
// run, and post-run hooks with the appropriate execution hooks.
106
func (c *CLI) applySubcommandHooks(
107
        cmd *cobra.Command,
108
        subcommands []keySubcommandTuple,
109
        errorMessage string,
110
        createConfig bool,
111
) {
24✔
112
        // In case we create a new project configuration we need to compute the plugin chain.
24✔
113
        pluginChain := make([]string, 0, len(c.resolvedPlugins))
24✔
114
        if createConfig {
30✔
115
                // We extract the plugin keys again instead of using the ones obtained when filtering subcommands
6✔
116
                // as these plugins are unbundled but we want to keep bundle names in the plugin chain.
6✔
117
                for _, p := range c.resolvedPlugins {
12✔
118
                        pluginChain = append(pluginChain, plugin.KeyFor(p))
6✔
119
                }
6✔
120
        }
121

122
        options := initializationHooks(cmd, subcommands, c.metadata())
24✔
123

24✔
124
        factory := executionHooksFactory{
24✔
125
                fs:             c.fs,
24✔
126
                store:          yamlstore.New(c.fs),
24✔
127
                subcommands:    subcommands,
24✔
128
                errorMessage:   errorMessage,
24✔
129
                projectVersion: c.projectVersion,
24✔
130
                pluginChain:    pluginChain,
24✔
131
        }
24✔
132
        cmd.PreRunE = factory.preRunEFunc(options, createConfig)
24✔
133
        cmd.RunE = factory.runEFunc()
24✔
134
        cmd.PostRunE = factory.postRunEFunc()
24✔
135
}
24✔
136

137
// initializationHooks executes update metadata and bind flags plugin hooks.
138
func initializationHooks(
139
        cmd *cobra.Command,
140
        subcommands []keySubcommandTuple,
141
        meta plugin.CLIMetadata,
142
) *resourceOptions {
143
        // Update metadata hook.
24✔
144
        subcmdMeta := plugin.SubcommandMetadata{
24✔
145
                Description: cmd.Long,
24✔
146
                Examples:    cmd.Example,
24✔
147
        }
24✔
148
        for _, tuple := range subcommands {
24✔
149
                if subcommand, updatesMetadata := tuple.subcommand.(plugin.UpdatesMetadata); updatesMetadata {
48✔
150
                        subcommand.UpdateMetadata(meta, &subcmdMeta)
48✔
151
                }
24✔
152
        }
24✔
153
        cmd.Long = subcmdMeta.Description
154
        cmd.Example = subcmdMeta.Examples
24✔
155

24✔
156
        // Before binding specific plugin flags, bind common ones.
24✔
157
        requiresResource := false
24✔
158
        for _, tuple := range subcommands {
24✔
159
                if _, requiresResource = tuple.subcommand.(plugin.RequiresResource); requiresResource {
48✔
160
                        break
36✔
161
                }
12✔
162
        }
163
        var options *resourceOptions
164
        if requiresResource {
24✔
165
                options = bindResourceFlags(cmd.Flags())
36✔
166
        }
12✔
167

12✔
168
        // Bind flags hook.
169
        for _, tuple := range subcommands {
170
                if subcommand, hasFlags := tuple.subcommand.(plugin.HasFlags); hasFlags {
48✔
171
                        subcommand.BindFlags(cmd.Flags())
48✔
172
                }
24✔
173
        }
24✔
174

175
        return options
176
}
24✔
177

178
type executionHooksFactory struct {
179
        // fs is the filesystem abstraction to scaffold files to.
180
        fs machinery.Filesystem
181
        // store is the backend used to load/save the project configuration.
182
        store store.Store
183
        // subcommands are the tuples representing the set of subcommands provided by the resolved plugins.
184
        subcommands []keySubcommandTuple
185
        // errorMessage is prepended to returned errors.
186
        errorMessage string
187
        // projectVersion is the project version that will be used to create new project configurations.
188
        // It is only used for initialization.
189
        projectVersion config.Version
190
        // pluginChain is the plugin chain configured for this project.
191
        pluginChain []string
192
}
193

194
func (factory *executionHooksFactory) forEach(cb func(subcommand plugin.Subcommand) error, errorMessage string) error {
195
        for i, tuple := range factory.subcommands {
196
                if tuple.skip {
197
                        continue
×
198
                }
×
199

×
200
                err := cb(tuple.subcommand)
×
201

202
                var exitError plugin.ExitError
203
                switch {
×
204
                case err == nil:
×
205
                        // No error do nothing
×
206
                case errors.As(err, &exitError):
×
207
                        // Exit errors imply that no further hooks of this subcommand should be called, so we flag it to be skipped
×
208
                        factory.subcommands[i].skip = true
209
                        fmt.Printf("skipping remaining hooks of %q: %s\n", tuple.key, exitError.Reason)
×
210
                default:
×
211
                        // Any other error, wrap it
×
212
                        return fmt.Errorf("%s: %s %q: %w", factory.errorMessage, errorMessage, tuple.key, err)
×
213
                }
×
214
        }
×
215

×
216
        return nil
217
}
218

219
// preRunEFunc returns a cobra RunE function that loads the configuration, creates the resource,
×
220
// and executes inject config, inject resource, and pre-scaffold hooks.
221
func (factory *executionHooksFactory) preRunEFunc(
222
        options *resourceOptions,
223
        createConfig bool,
224
) func(*cobra.Command, []string) error {
225
        return func(*cobra.Command, []string) error {
226
                if createConfig {
227
                        // Check if a project configuration is already present.
24✔
228
                        if err := factory.store.Load(); err == nil || !errors.Is(err, os.ErrNotExist) {
24✔
229
                                return fmt.Errorf("%s: already initialized", factory.errorMessage)
×
230
                        }
×
231

×
232
                        // Initialize the project configuration.
×
233
                        if err := factory.store.New(factory.projectVersion); err != nil {
×
234
                                return fmt.Errorf("%s: error initializing project configuration: %w", factory.errorMessage, err)
235
                        }
236
                } else {
×
237
                        // Load the project configuration.
×
238
                        if err := factory.store.Load(); os.IsNotExist(err) {
×
239
                                return fmt.Errorf("%s: unable to find configuration file, project must be initialized",
×
240
                                        factory.errorMessage)
×
241
                        } else if err != nil {
×
242
                                return fmt.Errorf("%s: unable to load configuration file: %w", factory.errorMessage, err)
×
243
                        }
×
244
                }
×
245
                cfg := factory.store.Config()
×
246

×
247
                // Set the pluginChain field.
248
                if len(factory.pluginChain) != 0 {
×
249
                        _ = cfg.SetPluginChain(factory.pluginChain)
×
250
                }
×
251

×
252
                // Create the resource if non-nil options provided
×
253
                var res *resource.Resource
×
254
                if options != nil {
255
                        // TODO: offer a flag instead of hard-coding project-wide domain
256
                        options.Domain = cfg.GetDomain()
×
257
                        if err := options.validate(); err != nil {
×
258
                                return fmt.Errorf("%s: unable to create resource: %w", factory.errorMessage, err)
×
259
                        }
260
                        res = options.newResource()
261
                }
×
262

×
263
                // Inject config hook.
×
264
                if err := factory.forEach(func(subcommand plugin.Subcommand) error {
×
265
                        if subcommand, requiresConfig := subcommand.(plugin.RequiresConfig); requiresConfig {
×
266
                                return subcommand.InjectConfig(cfg)
×
267
                        }
×
268
                        return nil
×
269
                }, "unable to inject the configuration to"); err != nil {
270
                        return err
271
                }
272

×
273
                if res != nil {
×
274
                        // Inject resource hook.
×
275
                        if err := factory.forEach(func(subcommand plugin.Subcommand) error {
×
276
                                if subcommand, requiresResource := subcommand.(plugin.RequiresResource); requiresResource {
×
277
                                        return subcommand.InjectResource(res)
×
278
                                }
×
279
                                return nil
×
280
                        }, "unable to inject the resource to"); err != nil {
281
                                return err
×
282
                        }
×
283

×
284
                        if err := res.Validate(); err != nil {
×
285
                                return fmt.Errorf("%s: created invalid resource: %w", factory.errorMessage, err)
×
286
                        }
×
287
                }
×
288

×
289
                // Pre-scaffold hook.
×
290
                //nolint:revive
×
291
                if err := factory.forEach(func(subcommand plugin.Subcommand) error {
292
                        if subcommand, hasPreScaffold := subcommand.(plugin.HasPreScaffold); hasPreScaffold {
×
293
                                return subcommand.PreScaffold(factory.fs)
×
294
                        }
×
295
                        return nil
296
                }, "unable to run pre-scaffold tasks of"); err != nil {
297
                        return err
298
                }
299

×
300
                return nil
×
301
        }
×
302
}
×
303

×
304
// runEFunc returns a cobra RunE function that executes the scaffold hook.
×
305
func (factory *executionHooksFactory) runEFunc() func(*cobra.Command, []string) error {
×
306
        return func(*cobra.Command, []string) error {
×
307
                // Scaffold hook.
308
                //nolint:revive
×
309
                if err := factory.forEach(func(subcommand plugin.Subcommand) error {
310
                        return subcommand.Scaffold(factory.fs)
311
                }, "unable to scaffold with"); err != nil {
312
                        return err
313
                }
24✔
314

24✔
315
                return nil
×
316
        }
×
317
}
×
318

×
319
// postRunEFunc returns a cobra RunE function that saves the configuration
×
320
// and executes the post-scaffold hook.
×
321
func (factory *executionHooksFactory) postRunEFunc() func(*cobra.Command, []string) error {
×
322
        return func(*cobra.Command, []string) error {
323
                if err := factory.store.Save(); err != nil {
×
324
                        return fmt.Errorf("%s: unable to save configuration file: %w", factory.errorMessage, err)
325
                }
326

327
                // Post-scaffold hook.
328
                //nolint:revive
329
                if err := factory.forEach(func(subcommand plugin.Subcommand) error {
24✔
330
                        if subcommand, hasPostScaffold := subcommand.(plugin.HasPostScaffold); hasPostScaffold {
24✔
331
                                return subcommand.PostScaffold()
×
332
                        }
×
333
                        return nil
×
334
                }, "unable to run post-scaffold tasks of"); err != nil {
335
                        return err
336
                }
337

×
338
                return nil
×
339
        }
×
340
}
×
341

×
342
func updateProjectFileForAlphaGenerate() error {
×
343
        projectFilePath := "PROJECT"
×
344

×
345
        content, err := os.ReadFile(projectFilePath)
346
        if err != nil {
×
347
                return fmt.Errorf("failed to read PROJECT file: %w", err)
348
        }
349

350
        projectStr := string(content)
×
351

×
352
        // Define outdated plugin versions that need replacement
×
353
        outdatedPlugins := []string{"go.kubebuilder.io/v3", "go.kubebuilder.io/v3-alpha", "go.kubebuilder.io/v2"}
×
354
        updated := false
×
355

×
356
        for _, oldPlugin := range outdatedPlugins {
×
357
                if strings.Contains(projectStr, oldPlugin) {
358
                        log.Warnf("Detected '%s' in PROJECT file.", oldPlugin)
×
359
                        log.Warnf("Kubebuilder v4 no longer supports this. It will be replaced with 'go.kubebuilder.io/v4'.")
×
360

×
361
                        projectStr = strings.ReplaceAll(projectStr, oldPlugin, "go.kubebuilder.io/v4")
×
362
                        updated = true
×
363
                        break
×
364
                }
×
365
        }
×
366

×
367
        // Only update the file if changes were made
×
368
        if updated {
×
369
                err = os.WriteFile(projectFilePath, []byte(projectStr), 0o644)
×
370
                if err != nil {
×
371
                        return fmt.Errorf("failed to update PROJECT file: %w", err)
×
372
                }
373
                log.Infof("PROJECT file updated successfully.")
374
        }
375

376
        return nil
×
377
}
×
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