diff --git a/cmd/main.go b/cmd/main.go index fa60babab3..7f59bb2616 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -46,17 +46,16 @@ func main() { "Please, check the migration guide to learn how to upgrade your project" // Bundle plugin which built the golang projects scaffold by Kubebuilder go/v3 - gov3Bundle, _ := plugin.NewBundle(golang.DefaultNameQualifier, plugin.Version{Number: 3}, - deprecateMessageGoV3Bundle, - kustomizecommonv1.Plugin{}, - golangv3.Plugin{}, + gov3Bundle, _ := plugin.NewBundleWithOptions(plugin.WithName(golang.DefaultNameQualifier), + plugin.WithVersion(plugin.Version{Number: 3}), + plugin.WithDeprecationMessage(deprecateMessageGoV3Bundle), + plugin.WithPlugins(kustomizecommonv1.Plugin{}, golangv3.Plugin{}), ) // Bundle plugin which built the golang projects scaffold by Kubebuilder go/v4 with kustomize alpha-v2 - gov4Bundle, _ := plugin.NewBundle(golang.DefaultNameQualifier, plugin.Version{Number: 4}, - "", - kustomizecommonv2alpha.Plugin{}, - golangv4.Plugin{}, + gov4Bundle, _ := plugin.NewBundleWithOptions(plugin.WithName(golang.DefaultNameQualifier), + plugin.WithVersion(plugin.Version{Number: 4}), + plugin.WithPlugins(kustomizecommonv2alpha.Plugin{}, golangv4.Plugin{}), ) fs := machinery.Filesystem{ diff --git a/docs/book/src/plugins/creating-plugins.md b/docs/book/src/plugins/creating-plugins.md index 4a175765f2..b9fbcb2936 100644 --- a/docs/book/src/plugins/creating-plugins.md +++ b/docs/book/src/plugins/creating-plugins.md @@ -57,9 +57,10 @@ Note that Kubebuilder provides the `kustomize.common.kubebuilder.io` to help in In this way, currently, you can [Extend the CLI][extending-cli] and use the `Bundle Plugin` to create your language plugins such as: ```go - mylanguagev1Bundle, _ := plugin.NewBundle(language.DefaultNameQualifier, plugin.Version{Number: 1}, - kustomizecommonv1.Plugin{}, // extend the common base from Kubebuilder - mylanguagev1.Plugin{}, // your plugin language which will do the scaffolds for the specific language on top of the common base + mylanguagev1Bundle, _ := plugin.NewBundle(plugin.WithName(language.DefaultNameQualifier), + plugin.WithVersion(plugin.Version{Number: 1}), + plugin.WithPlugins(kustomizecommonv1.Plugin{}, mylanguagev1.Plugin{}), // extend the common base from Kubebuilder + // your plugin language which will do the scaffolds for the specific language on top of the common base ) ``` @@ -169,9 +170,10 @@ See [example of deploy-image][example-of-deploy-image-3]. Alternatively, you can create a plugin bundle to include the target plugins. For instance: ```go - mylanguagev1Bundle, _ := plugin.NewBundle(language.DefaultNameQualifier, plugin.Version{Number: 1}, - kustomizecommonv1.Plugin{}, // extend the common base from Kuebebuilder - mylanguagev1.Plugin{}, // your plugin language which will do the scaffolds for the specific language on top of the common base + mylanguagev1Bundle, _ := plugin.NewBundle(plugin.WithName(language.DefaultNameQualifier), + plugin.WithVersion(plugin.Version{Number: 1}), + plugin.WithPlugins(kustomizecommonv1.Plugin{}, mylanguagev1.Plugin{}), // extend the common base from Kuebebuilder + // your plugin language which will do the scaffolds for the specific language on top of the common base ) ``` diff --git a/docs/book/src/plugins/extending-cli.md b/docs/book/src/plugins/extending-cli.md index 7a9f0e89b1..683e1c0197 100644 --- a/docs/book/src/plugins/extending-cli.md +++ b/docs/book/src/plugins/extending-cli.md @@ -42,9 +42,9 @@ var ( // GetPluginsCLI returns the plugins based CLI configured to be used in your CLI binary func GetPluginsCLI() (*cli.CLI) { // Bundle plugin which built the golang projects scaffold by Kubebuilder go/v3 - gov3Bundle, _ := plugin.NewBundle(golang.DefaultNameQualifier, plugin.Version{Number: 3}, - kustomizecommonv1.Plugin{}, - golangv3.Plugin{}, + gov3Bundle, _ := plugin.NewBundle(plugin.WithName(golang.DefaultNameQualifier), + plugin.WithVersion(plugin.Version{Number: 3}), + plugin.WithPlugins(kustomizecommonv1.Plugin{}, golangv3.Plugin{}), ) @@ -175,10 +175,9 @@ Once a plugin is deprecated, have it implement a [Deprecated][deprecate-plugin-d ```go // see that will be like myplugin.example/v1` - myPluginBundle, _ := plugin.NewBundle(``,``, - pluginA.Plugin{}, - pluginB.Plugin{}, - pluginC.Plugin{}, + myPluginBundle, _ := plugin.NewBundle(plugin.WithName(``), + plugin.WithVersion(``), + plugin.WithPlugins(pluginA.Plugin{}, pluginB.Plugin{}, pluginC.Plugin{}), ) ``` diff --git a/docs/book/src/plugins/kustomize-v1.md b/docs/book/src/plugins/kustomize-v1.md index 2440fc415f..b8c4d41351 100644 --- a/docs/book/src/plugins/kustomize-v1.md +++ b/docs/book/src/plugins/kustomize-v1.md @@ -59,9 +59,10 @@ all that is language specific and kustomize for its configuration, see: // Bundle plugin which built the golang projects scaffold by Kubebuilder go/v3 // The follow code is creating a new plugin with its name and version via composition // You can define that one plugin is composite by 1 or Many others plugins - gov3Bundle, _ := plugin.NewBundle(golang.DefaultNameQualifier, plugin.Version{Number: 3}, - kustomizecommonv1.Plugin{}, // scaffold the config/ directory and all kustomize files - golangv3.Plugin{}, // Scaffold the Golang files and all that specific for the language e.g. go.mod, apis, controllers + gov3Bundle, _ := plugin.NewBundle(plugin.WithName(golang.DefaultNameQualifier), + plugin.WithVersion(plugin.Version{Number: 3}), + plugin.WithPlugins(kustomizecommonv1.Plugin{}, golangv3.Plugin{}), // scaffold the config/ directory and all kustomize files + // Scaffold the Golang files and all that specific for the language e.g. go.mod, apis, controllers ) ``` diff --git a/docs/book/src/plugins/kustomize-v2.md b/docs/book/src/plugins/kustomize-v2.md index ad15b8dcd3..b8ae3d9348 100644 --- a/docs/book/src/plugins/kustomize-v2.md +++ b/docs/book/src/plugins/kustomize-v2.md @@ -44,9 +44,10 @@ import ( // Bundle plugin which built the golang projects scaffold by Kubebuilder go/v3 // The follow code is creating a new plugin with its name and version via composition // You can define that one plugin is composite by 1 or Many others plugins - gov3Bundle, _ := plugin.NewBundle(golang.DefaultNameQualifier, plugin.Version{Number: 3}, - kustomizecommonv2.Plugin{}, // scaffold the config/ directory and all kustomize files - golangv4.Plugin{}, // Scaffold the Golang files and all that specific for the language e.g. go.mod, apis, controllers + gov3Bundle, _ := plugin.NewBundle(plugin.WithName(golang.DefaultNameQualifier), + plugin.WithVersion(plugin.Version{Number: 3}), + plugin.WithPlugins(kustomizecommonv2.Plugin{}, golangv3.Plugin{}), // scaffold the config/ directory and all kustomize files + // Scaffold the Golang files and all that specific for the language e.g. go.mod, apis, controllers ) ``` diff --git a/pkg/plugin/bundle.go b/pkg/plugin/bundle.go index 8a4a9e657f..36d93e207a 100644 --- a/pkg/plugin/bundle.go +++ b/pkg/plugin/bundle.go @@ -31,6 +31,33 @@ type bundle struct { deprecateWarning string } +type BundleOption func(*bundle) + +func WithName(name string) BundleOption { + return func(opts *bundle) { + opts.name = name + } +} + +func WithVersion(version Version) BundleOption { + return func(opts *bundle) { + opts.version = version + } +} + +func WithPlugins(plugins ...Plugin) BundleOption { + return func(opts *bundle) { + opts.plugins = plugins + } +} + +func WithDeprecationMessage(msg string) BundleOption { + return func(opts *bundle) { + opts.deprecateWarning = msg + } + +} + // NewBundle creates a new Bundle with the provided name and version, and that wraps the provided plugins. // The list of supported project versions is computed from the provided plugins. func NewBundle(name string, version Version, deprecateWarning string, plugins ...Plugin) (Bundle, error) { @@ -60,6 +87,41 @@ func NewBundle(name string, version Version, deprecateWarning string, plugins .. }, nil } +// NewBundleWithOptions creates a new Bundle with the provided BundleOptions. +// The list of supported project versions is computed from the provided plugins in options. +func NewBundleWithOptions(opts ...BundleOption) (Bundle, error) { + bundleOpts := bundle{} + + for _, opts := range opts { + opts(&bundleOpts) + } + + supportedProjectVersions := CommonSupportedProjectVersions(bundleOpts.plugins...) + if len(supportedProjectVersions) == 0 { + return nil, fmt.Errorf("in order to bundle plugins, they must all support at least one common project version") + } + + // Plugins may be bundles themselves, so unbundle here + // NOTE(Adirio): unbundling here ensures that Bundle.Plugin always returns a flat list of Plugins instead of also + // including Bundles, and therefore we don't have to use a recursive algorithm when resolving. + allPlugins := make([]Plugin, 0, len(bundleOpts.plugins)) + for _, plugin := range bundleOpts.plugins { + if pluginBundle, isBundle := plugin.(Bundle); isBundle { + allPlugins = append(allPlugins, pluginBundle.Plugins()...) + } else { + allPlugins = append(allPlugins, plugin) + } + } + + return bundle{ + name: bundleOpts.name, + version: bundleOpts.version, + plugins: allPlugins, + supportedProjectVersions: supportedProjectVersions, + deprecateWarning: bundleOpts.deprecateWarning, + }, nil +} + // Name implements Plugin func (b bundle) Name() string { return b.name diff --git a/pkg/plugin/bundle_test.go b/pkg/plugin/bundle_test.go index c437bfd7b8..ccb1a41d57 100644 --- a/pkg/plugin/bundle_test.go +++ b/pkg/plugin/bundle_test.go @@ -118,4 +118,84 @@ var _ = Describe("Bundle", func() { } }) }) + + Context("NewBundleWithOptions", func() { + It("should succeed for plugins with common supported project versions", func() { + for _, plugins := range [][]Plugin{ + {p1, p2}, + {p1, p3}, + {p1, p4}, + {p2, p3}, + {p3, p4}, + + {p1, p2, p3}, + {p1, p3, p4}, + } { + b, err := NewBundleWithOptions(WithName(name), + WithVersion(version), + WithDeprecationMessage(""), + WithPlugins(plugins...), + ) + Expect(err).NotTo(HaveOccurred()) + Expect(b.Name()).To(Equal(name)) + Expect(b.Version().Compare(version)).To(Equal(0)) + versions := b.SupportedProjectVersions() + sort.Slice(versions, func(i int, j int) bool { + return versions[i].Compare(versions[j]) == -1 + }) + expectedVersions := CommonSupportedProjectVersions(plugins...) + sort.Slice(expectedVersions, func(i int, j int) bool { + return expectedVersions[i].Compare(expectedVersions[j]) == -1 + }) + Expect(versions).To(Equal(expectedVersions)) + Expect(b.Plugins()).To(Equal(plugins)) + } + }) + + It("should accept bundles as input", func() { + var a, b Bundle + var err error + plugins := []Plugin{p1, p2, p3} + a, err = NewBundleWithOptions(WithName("a"), + WithVersion(version), + WithDeprecationMessage(""), + WithPlugins(p1, p2), + ) + Expect(err).NotTo(HaveOccurred()) + b, err = NewBundleWithOptions(WithName("b"), + WithVersion(version), + WithDeprecationMessage(""), + WithPlugins(a, p3), + ) + Expect(err).NotTo(HaveOccurred()) + versions := b.SupportedProjectVersions() + sort.Slice(versions, func(i int, j int) bool { + return versions[i].Compare(versions[j]) == -1 + }) + expectedVersions := CommonSupportedProjectVersions(plugins...) + sort.Slice(expectedVersions, func(i int, j int) bool { + return expectedVersions[i].Compare(expectedVersions[j]) == -1 + }) + Expect(versions).To(Equal(expectedVersions)) + Expect(b.Plugins()).To(Equal(plugins)) + }) + + It("should fail for plugins with no common supported project version", func() { + for _, plugins := range [][]Plugin{ + {p2, p4}, + + {p1, p2, p4}, + {p2, p3, p4}, + + {p1, p2, p3, p4}, + } { + _, err := NewBundleWithOptions(WithName(name), + WithVersion(version), + WithDeprecationMessage(""), + WithPlugins(plugins...), + ) + Expect(err).To(HaveOccurred()) + } + }) + }) })