Skip to content

Commit 8469e62

Browse files
authored
Merge pull request #4844 from camilamacedo86/fix-command-options
🐛 (cli) fix: allow 'alpha generate' to work with unsupported plugins by patching PROJECT file in-memory
2 parents cc59c89 + 4ccbe2e commit 8469e62

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)