@@ -22,6 +22,8 @@ import (
22
22
"os"
23
23
"strings"
24
24
25
+ log "github.com/sirupsen/logrus"
26
+
25
27
"github.com/spf13/afero"
26
28
"github.com/spf13/cobra"
27
29
"github.com/spf13/pflag"
@@ -149,14 +151,6 @@ func (c *CLI) buildCmd() error {
149
151
150
152
var uve config.UnsupportedVersionError
151
153
152
- // Workaround for kubebuilder alpha generate
153
- if len (os .Args ) > 2 && os .Args [1 ] == "alpha" && os .Args [2 ] == "generate" {
154
- err := updateProjectFileForAlphaGenerate ()
155
- if err != nil {
156
- return fmt .Errorf ("failed to update PROJECT file: %w" , err )
157
- }
158
- }
159
-
160
154
// Get project version and plugin keys.
161
155
switch err := c .getInfo (); {
162
156
case err == nil :
@@ -217,13 +211,113 @@ func (c *CLI) getInfo() error {
217
211
func (c * CLI ) getInfoFromConfigFile () error {
218
212
// Read the project configuration file
219
213
cfg := yamlstore .New (c .fs )
214
+
215
+ // Workaround for https://github.com/kubernetes-sigs/kubebuilder/issues/4433
216
+ //
217
+ // This allows the `kubebuilder alpha generate` command to work with old projects
218
+ // that use plugin versions no longer supported (like go.kubebuilder.io/v3).
219
+ //
220
+ // We read the PROJECT file into memory and update the plugin version (e.g. from v3 to v4)
221
+ // before the CLI tries to load it. This avoids errors during config loading
222
+ // and lets users migrate their project layout from go/v3 to go/v4.
223
+
224
+ if isAlphaGenerateCommand (os .Args [1 :]) {
225
+ // Patch raw file bytes before unmarshalling
226
+ if err := patchProjectFileInMemoryIfNeeded (c .fs .FS , yamlstore .DefaultPath ); err != nil {
227
+ return err
228
+ }
229
+ }
230
+
220
231
if err := cfg .Load (); err != nil {
221
232
return fmt .Errorf ("error loading configuration: %w" , err )
222
233
}
223
234
224
235
return c .getInfoFromConfig (cfg .Config ())
225
236
}
226
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 {
241
+ positional := []string {}
242
+ skip := false
243
+
244
+ for i := 0 ; i < len (args ); i ++ {
245
+ arg := args [i ]
246
+
247
+ // Skip flags and their values
248
+ if strings .HasPrefix (arg , "-" ) {
249
+ // If the flag is in --flag=value format, skip only this one
250
+ if strings .Contains (arg , "=" ) {
251
+ continue
252
+ }
253
+ // If it's --flag value format, skip next one too
254
+ if i + 1 < len (args ) && ! strings .HasPrefix (args [i + 1 ], "-" ) {
255
+ skip = true
256
+ }
257
+ continue
258
+ }
259
+ if skip {
260
+ skip = false
261
+ continue
262
+ }
263
+ positional = append (positional , arg )
264
+ }
265
+
266
+ // Check for `alpha generate` in positional arguments
267
+ for i := 0 ; i < len (positional )- 1 ; i ++ {
268
+ if positional [i ] == "alpha" && positional [i + 1 ] == "generate" {
269
+ return true
270
+ }
271
+ }
272
+
273
+ return false
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.
282
+ func patchProjectFileInMemoryIfNeeded (fs afero.Fs , path string ) error {
283
+ type pluginReplacement struct {
284
+ Old string
285
+ New string
286
+ }
287
+
288
+ replacements := []pluginReplacement {
289
+ {"go.kubebuilder.io/v2" , "go.kubebuilder.io/v4" },
290
+ {"go.kubebuilder.io/v3" , "go.kubebuilder.io/v4" },
291
+ {"go.kubebuilder.io/v3-alpha" , "go.kubebuilder.io/v4" },
292
+ }
293
+
294
+ content , err := afero .ReadFile (fs , path )
295
+ if err != nil {
296
+ return nil
297
+ }
298
+
299
+ original := string (content )
300
+ modified := original
301
+
302
+ for _ , rep := range replacements {
303
+ if strings .Contains (modified , rep .Old ) {
304
+ modified = strings .ReplaceAll (modified , rep .Old , rep .New )
305
+ log .Warnf ("This project is using an old and no longer supported plugin layout %q. " +
306
+ "Replace in memory to %q to allow `alpha generate` to work." ,
307
+ rep .Old , rep .New )
308
+ }
309
+ }
310
+
311
+ if modified != original {
312
+ err := afero .WriteFile (fs , path , []byte (modified ), 0o755 )
313
+ if err != nil {
314
+ return fmt .Errorf ("failed to write patched PROJECT file: %w" , err )
315
+ }
316
+ }
317
+
318
+ return nil
319
+ }
320
+
227
321
// getInfoFromConfig obtains the project version and plugin keys from the project config.
228
322
// It is extracted from getInfoFromConfigFile for testing purposes.
229
323
func (c * CLI ) getInfoFromConfig (projectConfig config.Config ) error {
0 commit comments