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

kubernetes-sigs / kubebuilder / 14286290433

31 Mar 2025 10:22PM UTC coverage: 71.895%. Remained the same
14286290433

Pull #4612

github

vitorfloriano
🌱 Add kubernetesVendorVersion for binary builds with LD_FLAGS

This commit adds a kubernetesVendorVersion flag to Makefile.

Rely on ldflags to set kubernetesVendorVersion, similarly to the other variables in cmd/version.go.

Use a single variable to define K8S_VERSION for both ldflags and the goreleaser configuration.
Pull Request #4612: 🌱 Add kubernetesVendorVersion for binary builds with LD_FLAGS

2310 of 3213 relevant lines covered (71.9%)

16.61 hits per line

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

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

24
        "github.com/spf13/cobra"
25

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

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

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

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

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

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

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

1✔
66
// keySubcommandTuple represents a pairing of the key of a plugin with a plugin.Subcommand.
67
type keySubcommandTuple struct {
68
        key        string
69
        subcommand plugin.Subcommand
70

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

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

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

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

6✔
120
        options := initializationHooks(cmd, subcommands, c.metadata())
121

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

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

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

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

24✔
173
        return options
174
}
175

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

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

×
198
                err := cb(tuple.subcommand)
199

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

214
        return nil
215
}
216

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

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

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

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

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

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

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

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

×
298
                return nil
×
299
        }
300
}
×
301

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

×
313
                return nil
×
314
        }
315
}
×
316

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

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

×
336
                return nil
×
337
        }
338
}
×
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