Skip to content

Commit 4ccbe2e

Browse files
(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: #4433
1 parent 72a5ab5 commit 4ccbe2e

File tree

3 files changed

+116
-48
lines changed

3 files changed

+116
-48
lines changed

.github/workflows/test-alpha-generate.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ jobs:
3535
run: |
3636
sed -i 's#go.kubebuilder.io/v4#go.kubebuilder.io/v3#g' testdata/project-v4/PROJECT
3737
38+
# Validate if help output is working and workaround to
39+
# update the PROJECT file in memory to allow upgrade
40+
# no longer supported layouts did not break the command options
41+
- name: Validate help output for alpha generate
42+
run: |
43+
if kubebuilder alpha generate --help | grep -q "kubebuilder alpha generate \[flags\]"; then
44+
echo "Help output validated"
45+
else
46+
echo "Help output missing or invalid"
47+
exit 1
48+
fi
49+
3850
- name: Run kubebuilder alpha generate
3951
run: |
40-
cd testdata/project-v4 && kubebuilder alpha generate
52+
cd testdata/project-v4 && kubebuilder alpha generate
53+

pkg/cli/cli.go

Lines changed: 102 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"os"
2323
"strings"
2424

25+
log "github.com/sirupsen/logrus"
26+
2527
"github.com/spf13/afero"
2628
"github.com/spf13/cobra"
2729
"github.com/spf13/pflag"
@@ -149,14 +151,6 @@ func (c *CLI) buildCmd() error {
149151

150152
var uve config.UnsupportedVersionError
151153

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-
160154
// Get project version and plugin keys.
161155
switch err := c.getInfo(); {
162156
case err == nil:
@@ -217,13 +211,113 @@ func (c *CLI) getInfo() error {
217211
func (c *CLI) getInfoFromConfigFile() error {
218212
// Read the project configuration file
219213
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+
220231
if err := cfg.Load(); err != nil {
221232
return fmt.Errorf("error loading configuration: %w", err)
222233
}
223234

224235
return c.getInfoFromConfig(cfg.Config())
225236
}
226237

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+
227321
// getInfoFromConfig obtains the project version and plugin keys from the project config.
228322
// It is extracted from getInfoFromConfigFile for testing purposes.
229323
func (c *CLI) getInfoFromConfig(projectConfig config.Config) error {

pkg/cli/cmd_helpers.go

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ import (
2020
"errors"
2121
"fmt"
2222
"os"
23-
"strings"
2423

25-
log "github.com/sirupsen/logrus"
2624
"github.com/spf13/cobra"
2725

2826
"sigs.k8s.io/kubebuilder/v4/pkg/config"
@@ -346,40 +344,3 @@ func (factory *executionHooksFactory) postRunEFunc() func(*cobra.Command, []stri
346344
return nil
347345
}
348346
}
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-
}

0 commit comments

Comments
 (0)