Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 101 additions & 13 deletions pkg/cli/alpha/internal/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,12 +243,41 @@ func kubebuilderCreate(s store.Store) error {
// Migrates the Grafana plugin.
func migrateGrafanaPlugin(s store.Store, src, des string) error {
var grafanaPlugin struct{}
err := s.Config().DecodePluginConfig(plugin.KeyFor(grafanav1alpha.Plugin{}), grafanaPlugin)
if errors.As(err, &config.PluginKeyNotFoundError{}) {
key := plugin.GetPluginKeyForConfig(s.Config().GetPluginChain(), grafanav1alpha.Plugin{})
canonicalKey := plugin.KeyFor(grafanav1alpha.Plugin{})
found := true
var err error

if err = s.Config().DecodePluginConfig(key, grafanaPlugin); err != nil {
switch {
case errors.As(err, &config.PluginKeyNotFoundError{}):
found = false
if key != canonicalKey {
if err = s.Config().DecodePluginConfig(canonicalKey, grafanaPlugin); err != nil {
switch {
case errors.As(err, &config.PluginKeyNotFoundError{}):
// still not found
case errors.As(err, &config.UnsupportedFieldError{}):
slog.Info("Project config version does not support plugin metadata, skipping Grafana migration")
return nil
default:
return fmt.Errorf("failed to decode grafana plugin config: %w", err)
}
} else {
found = true
}
}
case errors.As(err, &config.UnsupportedFieldError{}):
slog.Info("Project config version does not support plugin metadata, skipping Grafana migration")
return nil
default:
return fmt.Errorf("failed to decode grafana plugin config: %w", err)
}
}

if !found {
slog.Info("Grafana plugin not found, skipping migration")
return nil
} else if err != nil {
return fmt.Errorf("failed to decode grafana plugin config: %w", err)
}

if err = kubebuilderGrafanaEdit(); err != nil {
Expand All @@ -264,30 +293,89 @@ func migrateGrafanaPlugin(s store.Store, src, des string) error {

func migrateAutoUpdatePlugin(s store.Store) error {
var autoUpdatePlugin struct{}
err := s.Config().DecodePluginConfig(plugin.KeyFor(autoupdatev1alpha.Plugin{}), autoUpdatePlugin)
if errors.As(err, &config.PluginKeyNotFoundError{}) {
key := plugin.GetPluginKeyForConfig(s.Config().GetPluginChain(), autoupdatev1alpha.Plugin{})
canonicalKey := plugin.KeyFor(autoupdatev1alpha.Plugin{})
found := true
var err error

if err = s.Config().DecodePluginConfig(key, autoUpdatePlugin); err != nil {
switch {
case errors.As(err, &config.PluginKeyNotFoundError{}):
found = false
if key != canonicalKey {
if err = s.Config().DecodePluginConfig(canonicalKey, autoUpdatePlugin); err != nil {
switch {
case errors.As(err, &config.PluginKeyNotFoundError{}):
// still not found
case errors.As(err, &config.UnsupportedFieldError{}):
slog.Info("Project config version does not support plugin metadata, skipping Auto Update migration")
return nil
default:
return fmt.Errorf("failed to decode autoupdate plugin config: %w", err)
}
} else {
found = true
}
}
case errors.As(err, &config.UnsupportedFieldError{}):
slog.Info("Project config version does not support plugin metadata, skipping Auto Update migration")
return nil
default:
return fmt.Errorf("failed to decode autoupdate plugin config: %w", err)
}
}

if !found {
slog.Info("Auto Update plugin not found, skipping migration")
return nil
} else if err != nil {
return fmt.Errorf("failed to decode autoupdate plugin config: %w", err)
}

args := []string{"edit", "--plugins", plugin.KeyFor(autoupdatev1alpha.Plugin{})}
if err := util.RunCmd("kubebuilder edit", "kubebuilder", args...); err != nil {
if err = util.RunCmd("kubebuilder edit", "kubebuilder", args...); err != nil {
return fmt.Errorf("failed to run edit subcommand for Auto plugin: %w", err)
}
return nil
}

// Migrates the Deploy Image plugin.
func migrateDeployImagePlugin(s store.Store) error {
key := plugin.GetPluginKeyForConfig(s.Config().GetPluginChain(), deployimagev1alpha1.Plugin{})
canonicalKey := plugin.KeyFor(deployimagev1alpha1.Plugin{})
var deployImagePlugin deployimagev1alpha1.PluginConfig
err := s.Config().DecodePluginConfig(plugin.KeyFor(deployimagev1alpha1.Plugin{}), &deployImagePlugin)
if errors.As(err, &config.PluginKeyNotFoundError{}) {
found := true

var err error
err = s.Config().DecodePluginConfig(key, &deployImagePlugin)
if err != nil {
switch {
case errors.As(err, &config.PluginKeyNotFoundError{}):
found = false
if key != canonicalKey {
if err = s.Config().DecodePluginConfig(canonicalKey, &deployImagePlugin); err != nil {
switch {
case errors.As(err, &config.PluginKeyNotFoundError{}):
// still not found
case errors.As(err, &config.UnsupportedFieldError{}):
slog.Info("Project config version does not support plugin metadata, skipping Deploy Image migration")
return nil
default:
return fmt.Errorf("failed to decode deploy-image plugin config: %w", err)
}
} else {
found = true
}
}
case errors.As(err, &config.UnsupportedFieldError{}):
slog.Info("Project config version does not support plugin metadata, skipping Deploy Image migration")
return nil
default:
return fmt.Errorf("failed to decode deploy-image plugin config: %w", err)
}
}

if !found {
slog.Info("Deploy-image plugin not found, skipping migration")
return nil
} else if err != nil {
return fmt.Errorf("failed to decode deploy-image plugin config: %w", err)
}

for _, r := range deployImagePlugin.Resources {
Expand Down
128 changes: 128 additions & 0 deletions pkg/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"sigs.k8s.io/kubebuilder/v4/pkg/config"
cfgv3 "sigs.k8s.io/kubebuilder/v4/pkg/config/v3"
"sigs.k8s.io/kubebuilder/v4/pkg/machinery"
"sigs.k8s.io/kubebuilder/v4/pkg/model/resource"
"sigs.k8s.io/kubebuilder/v4/pkg/model/stage"
"sigs.k8s.io/kubebuilder/v4/pkg/plugin"
golangv4 "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4"
Expand Down Expand Up @@ -89,6 +90,56 @@ func (s *pluginChainCapturingSubcommand) SetPluginChain(chain []string) {
s.pluginChain = append([]string(nil), chain...)
}

type testCreateAPIPlugin struct {
name string
version plugin.Version
subcommand *testCreateAPISubcommand
projectVers []config.Version
}

func newTestCreateAPIPlugin(name string, version plugin.Version) testCreateAPIPlugin {
return testCreateAPIPlugin{
name: name,
version: version,
subcommand: &testCreateAPISubcommand{},
projectVers: []config.Version{{Number: 3}},
}
}

func (p testCreateAPIPlugin) Name() string { return p.name }
func (p testCreateAPIPlugin) Version() plugin.Version { return p.version }
func (p testCreateAPIPlugin) SupportedProjectVersions() []config.Version { return p.projectVers }
func (p testCreateAPIPlugin) GetCreateAPISubcommand() plugin.CreateAPISubcommand {
return p.subcommand
}

type testCreateAPISubcommand struct{}

func (s *testCreateAPISubcommand) InjectResource(*resource.Resource) error {
return nil
}

func (s *testCreateAPISubcommand) Scaffold(machinery.Filesystem) error {
return nil
}

type fakeStore struct {
cfg config.Config
}

func (f *fakeStore) New(config.Version) error { return nil }
func (f *fakeStore) Load() error { return nil }
func (f *fakeStore) LoadFrom(string) error { return nil }
func (f *fakeStore) Save() error { return nil }
func (f *fakeStore) SaveTo(string) error { return nil }
func (f *fakeStore) Config() config.Config { return f.cfg }

type captureSubcommand struct {
lastChain []string
}

func (c *captureSubcommand) Scaffold(machinery.Filesystem) error { return nil }

var _ = Describe("CLI", func() {
var (
c *CLI
Expand All @@ -103,6 +154,83 @@ var _ = Describe("CLI", func() {
projectVersion = config.Version{Number: 3}
})

Describe("filterSubcommands", func() {
It("propagates bundle keys to wrapped subcommands", func() {
bundleVersion := plugin.Version{Number: 1, Stage: stage.Alpha}

fooPlugin := newTestCreateAPIPlugin("deploy-image.go.kubebuilder.io", plugin.Version{Number: 1, Stage: stage.Alpha})
barPlugin := newTestCreateAPIPlugin("deploy-image.go.kubebuilder.io", plugin.Version{Number: 1, Stage: stage.Alpha})

fooBundle, err := plugin.NewBundleWithOptions(
plugin.WithName("deploy-image.foo.example.com"),
plugin.WithVersion(bundleVersion),
plugin.WithPlugins(fooPlugin),
)
Expect(err).NotTo(HaveOccurred())

barBundle, err := plugin.NewBundleWithOptions(
plugin.WithName("deploy-image.bar.example.com"),
plugin.WithVersion(bundleVersion),
plugin.WithPlugins(barPlugin),
)
Expect(err).NotTo(HaveOccurred())

c.resolvedPlugins = []plugin.Plugin{fooBundle, barBundle}

tuples := c.filterSubcommands(
func(p plugin.Plugin) bool {
_, isCreateAPI := p.(plugin.CreateAPI)
return isCreateAPI
},
func(p plugin.Plugin) plugin.Subcommand {
return p.(plugin.CreateAPI).GetCreateAPISubcommand()
},
)

Expect(tuples).To(HaveLen(2))
Expect(tuples[0].key).To(Equal("deploy-image.go.kubebuilder.io/v1-alpha"))
Expect(tuples[0].configKey).To(Equal("deploy-image.foo.example.com/v1-alpha"))
Expect(tuples[1].key).To(Equal("deploy-image.go.kubebuilder.io/v1-alpha"))
Expect(tuples[1].configKey).To(Equal("deploy-image.bar.example.com/v1-alpha"))
})
})

Describe("executionHooksFactory", func() {
It("temporarily reorders the plugin chain while invoking bundled subcommands", func() {
cfg := cfgv3.New()
Expect(cfg.SetPluginChain([]string{
"deploy-image.foo.example.com/v1-alpha",
"deploy-image.bar.example.com/v1-alpha",
})).To(Succeed())

store := &fakeStore{cfg: cfg}
first := &captureSubcommand{}
second := &captureSubcommand{}

factory := executionHooksFactory{
store: store,
subcommands: []keySubcommandTuple{
{configKey: "deploy-image.foo.example.com/v1-alpha", subcommand: first},
{configKey: "deploy-image.bar.example.com/v1-alpha", subcommand: second},
},
errorMessage: "test",
}

callErr := factory.forEach(func(sub plugin.Subcommand) error {
cs := sub.(*captureSubcommand)
cs.lastChain = append([]string(nil), store.Config().GetPluginChain()...)
return nil
}, "scaffold")
Expect(callErr).NotTo(HaveOccurred())
Expect(first.lastChain[0]).To(Equal("deploy-image.foo.example.com/v1-alpha"))
Expect(second.lastChain[0]).To(Equal("deploy-image.bar.example.com/v1-alpha"))
Expect(store.Config().GetPluginChain()).To(Equal([]string{
"deploy-image.foo.example.com/v1-alpha",
"deploy-image.bar.example.com/v1-alpha",
}))
})
})

Context("buildCmd", func() {
var projectFile string

Expand Down
Loading