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

kubernetes-sigs / kubebuilder / 14879037873

07 May 2025 08:42AM UTC coverage: 71.57%. Remained the same
14879037873

Pull #4797

github

web-flow
:seedling: Bump golang.org/x/tools from 0.32.0 to 0.33.0

Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.32.0 to 0.33.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.32.0...v0.33.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-version: 0.33.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #4797: 🌱 Bump golang.org/x/tools from 0.32.0 to 0.33.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
                cliVersion:     c.cliVersion,
24✔
132
        }
24✔
133
        cmd.PreRunE = factory.preRunEFunc(options, createConfig)
24✔
134
        cmd.RunE = factory.runEFunc()
24✔
135
        cmd.PostRunE = factory.postRunEFunc()
24✔
136
}
137

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

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

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

176
        return options
24✔
177
}
178

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

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

203
                err := cb(tuple.subcommand)
×
204

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

219
        return nil
×
220
}
221

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

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

×
250
                // Set the CLI version if creating a new project configuration.
×
251
                if createConfig {
×
252
                        _ = cfg.SetCliVersion(factory.cliVersion)
×
253
                }
×
254

255
                // Set the pluginChain field.
256
                if len(factory.pluginChain) != 0 {
×
257
                        _ = cfg.SetPluginChain(factory.pluginChain)
×
258
                }
×
259

260
                // Create the resource if non-nil options provided
261
                var res *resource.Resource
×
262
                if options != nil {
×
263
                        // TODO: offer a flag instead of hard-coding project-wide domain
×
264
                        options.Domain = cfg.GetDomain()
×
265
                        if err := options.validate(); err != nil {
×
266
                                return fmt.Errorf("%s: unable to create resource: %w", factory.errorMessage, err)
×
267
                        }
×
268
                        res = options.newResource()
×
269
                }
270

271
                // Inject config hook.
272
                if err := factory.forEach(func(subcommand plugin.Subcommand) error {
×
273
                        if subcommand, requiresConfig := subcommand.(plugin.RequiresConfig); requiresConfig {
×
274
                                return subcommand.InjectConfig(cfg)
×
275
                        }
×
276
                        return nil
×
277
                }, "unable to inject the configuration to"); err != nil {
×
278
                        return err
×
279
                }
×
280

281
                if res != nil {
×
282
                        // Inject resource hook.
×
283
                        if err := factory.forEach(func(subcommand plugin.Subcommand) error {
×
284
                                if subcommand, requiresResource := subcommand.(plugin.RequiresResource); requiresResource {
×
285
                                        return subcommand.InjectResource(res)
×
286
                                }
×
287
                                return nil
×
288
                        }, "unable to inject the resource to"); err != nil {
×
289
                                return err
×
290
                        }
×
291

292
                        if err := res.Validate(); err != nil {
×
293
                                return fmt.Errorf("%s: created invalid resource: %w", factory.errorMessage, err)
×
294
                        }
×
295
                }
296

297
                // Pre-scaffold hook.
298
                //nolint:revive
299
                if err := factory.forEach(func(subcommand plugin.Subcommand) error {
×
300
                        if subcommand, hasPreScaffold := subcommand.(plugin.HasPreScaffold); hasPreScaffold {
×
301
                                return subcommand.PreScaffold(factory.fs)
×
302
                        }
×
303
                        return nil
×
304
                }, "unable to run pre-scaffold tasks of"); err != nil {
×
305
                        return err
×
306
                }
×
307

308
                return nil
×
309
        }
310
}
311

312
// runEFunc returns a cobra RunE function that executes the scaffold hook.
313
func (factory *executionHooksFactory) runEFunc() func(*cobra.Command, []string) error {
24✔
314
        return func(*cobra.Command, []string) error {
24✔
315
                // Scaffold hook.
×
316
                //nolint:revive
×
317
                if err := factory.forEach(func(subcommand plugin.Subcommand) error {
×
318
                        return subcommand.Scaffold(factory.fs)
×
319
                }, "unable to scaffold with"); err != nil {
×
320
                        return err
×
321
                }
×
322

323
                return nil
×
324
        }
325
}
326

327
// postRunEFunc returns a cobra RunE function that saves the configuration
328
// and executes the post-scaffold hook.
329
func (factory *executionHooksFactory) postRunEFunc() func(*cobra.Command, []string) error {
24✔
330
        return func(*cobra.Command, []string) error {
24✔
331
                if err := factory.store.Save(); err != nil {
×
332
                        return fmt.Errorf("%s: unable to save configuration file: %w", factory.errorMessage, err)
×
333
                }
×
334

335
                // Post-scaffold hook.
336
                //nolint:revive
337
                if err := factory.forEach(func(subcommand plugin.Subcommand) error {
×
338
                        if subcommand, hasPostScaffold := subcommand.(plugin.HasPostScaffold); hasPostScaffold {
×
339
                                return subcommand.PostScaffold()
×
340
                        }
×
341
                        return nil
×
342
                }, "unable to run post-scaffold tasks of"); err != nil {
×
343
                        return err
×
344
                }
×
345

346
                return nil
×
347
        }
348
}
349

350
func updateProjectFileForAlphaGenerate() error {
×
351
        projectFilePath := "PROJECT"
×
352

×
353
        content, err := os.ReadFile(projectFilePath)
×
354
        if err != nil {
×
355
                return fmt.Errorf("failed to read PROJECT file: %w", err)
×
356
        }
×
357

358
        projectStr := string(content)
×
359

×
360
        // Define outdated plugin versions that need replacement
×
361
        outdatedPlugins := []string{"go.kubebuilder.io/v3", "go.kubebuilder.io/v3-alpha", "go.kubebuilder.io/v2"}
×
362
        updated := false
×
363

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

×
369
                        projectStr = strings.ReplaceAll(projectStr, oldPlugin, "go.kubebuilder.io/v4")
×
370
                        updated = true
×
371
                        break
×
372
                }
373
        }
374

375
        // Only update the file if changes were made
376
        if updated {
×
377
                err = os.WriteFile(projectFilePath, []byte(projectStr), 0o644)
×
378
                if err != nil {
×
379
                        return fmt.Errorf("failed to update PROJECT file: %w", err)
×
380
                }
×
381
                log.Infof("PROJECT file updated successfully.")
×
382
        }
383

384
        return nil
×
385
}
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