From f460cc77f4b57e2da9bf216d7f8be217f0d28319 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 27 Feb 2020 14:01:24 -0800 Subject: [PATCH] pkg/cli: use --plugins to determine which plugin.Base's kubebuilder should run for a subcommand. This option is global. Refactoring of getBaseFlags -> parseBaseFlags allows multiple global flags to be parsed for a cli struct. plugin filtering considers short names, ex. --plugins="go" rather than --plugins="go.kubebuilder.io/v2.0.0". WithDefaultPlugins sets the default plugins a CLi should use in case --plugins or the config 'layout' are not set pkg/model/config: Config support the 'layout' key which indicates the plugin that scaffolded a project, by plugin key pkg/plugin: SplitKey, KeyFor, Key, GetShortName are utility functions for plugin keys * pkg/{cli,plugin}: add plugin name validation * pkg/plugin/{v1,v2}: set layout key in config Please enter the commit message for your changes. Lines starting --- cmd/main.go | 4 + ...ble-cli-and-scaffolding-plugins-phase-1.md | 2 +- pkg/cli/api.go | 7 +- pkg/cli/cli.go | 292 +++++++++++++++--- pkg/cli/init.go | 41 +-- pkg/cli/webhook.go | 7 +- pkg/model/config/config.go | 3 + pkg/plugin/plugin.go | 33 +- pkg/plugin/v1/init.go | 2 +- pkg/plugin/v1/plugin.go | 45 +-- pkg/plugin/v2/init.go | 4 +- pkg/plugin/v2/plugin.go | 36 +-- pkg/plugin/validation.go | 10 + testdata/project-v2-addon/PROJECT | 1 + testdata/project-v2-multigroup/PROJECT | 1 + testdata/project-v2/PROJECT | 1 + 16 files changed, 342 insertions(+), 147 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 743608350f5..e79b6611b8b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -31,6 +31,10 @@ func main() { &pluginv1.Plugin{}, &pluginv2.Plugin{}, ), + cli.WithDefaultPlugins( + &pluginv1.Plugin{}, + &pluginv2.Plugin{}, + ), cli.WithExtraCommands( newEditCmd(), newUpdateCmd(), diff --git a/designs/extensible-cli-and-scaffolding-plugins-phase-1.md b/designs/extensible-cli-and-scaffolding-plugins-phase-1.md index f474facf938..5f1fbd0f5ce 100644 --- a/designs/extensible-cli-and-scaffolding-plugins-phase-1.md +++ b/designs/extensible-cli-and-scaffolding-plugins-phase-1.md @@ -62,7 +62,7 @@ project versions (via `SupportedProjectVersions()`). Example `PROJECT` file: ```yaml -version: "3-alpha" +version: "2" layout: go/v1.0.0 domain: testproject.org repo: github.com/test-inc/testproject diff --git a/pkg/cli/api.go b/pkg/cli/api.go index 0c1f6a938cf..e65f3a33720 100644 --- a/pkg/cli/api.go +++ b/pkg/cli/api.go @@ -55,14 +55,9 @@ func (c cli) newAPIContext() plugin.Context { } func (c cli) bindCreateAPI(ctx plugin.Context, cmd *cobra.Command) { - versionedPlugins, err := c.getVersionedPlugins() - if err != nil { - cmdErr(cmd, err) - return - } var getter plugin.CreateAPIPluginGetter var hasGetter bool - for _, p := range versionedPlugins { + for _, p := range c.resolvedPlugins { tmpGetter, isGetter := p.(plugin.CreateAPIPluginGetter) if isGetter { if hasGetter { diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 26d7e7f18e2..8614779bdf2 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -17,12 +17,13 @@ limitations under the License. package cli import ( - "errors" "fmt" "log" "os" + "strings" "github.com/spf13/cobra" + "github.com/spf13/pflag" internalconfig "sigs.k8s.io/kubebuilder/internal/config" "sigs.k8s.io/kubebuilder/pkg/internal/validation" @@ -35,6 +36,10 @@ const ( runInProjectRootMsg = `For project-specific information, run this command in the root directory of a project. ` + + projectVersionFlag = "project-version" + helpFlag = "help" + pluginNamesFlag = "plugins" ) // CLI interacts with a command line interface. @@ -58,21 +63,32 @@ type cli struct { projectVersion string // True if the project has config file. configured bool + // Whether the command is requesting help. + doGenericHelp bool + + // Plugins injected by options. + pluginsFromOptions map[string][]plugin.Base + // Default plugins injected by options. + defaultPluginsFromOptions map[string][]plugin.Base + // A mapping of plugin name to version passed by to --plugins. + cliPluginKeys map[string]string + // A filtered set of plugins that should be used by command constructors. + resolvedPlugins []plugin.Base // Base command. cmd *cobra.Command // Commands injected by options. extraCommands []*cobra.Command - // Plugins injected by options. - plugins map[string][]plugin.Base } // New creates a new cli instance. func New(opts ...Option) (CLI, error) { c := &cli{ - commandName: "kubebuilder", - defaultProjectVersion: internalconfig.DefaultVersion, - plugins: map[string][]plugin.Base{}, + commandName: "kubebuilder", + defaultProjectVersion: internalconfig.DefaultVersion, + pluginsFromOptions: make(map[string][]plugin.Base), + defaultPluginsFromOptions: make(map[string][]plugin.Base), + cliPluginKeys: make(map[string]string), } for _, opt := range opts { if err := opt(c); err != nil { @@ -113,10 +129,25 @@ func WithPlugins(plugins ...plugin.Base) Option { return func(c *cli) error { for _, p := range plugins { for _, version := range p.SupportedProjectVersions() { - if _, ok := c.plugins[version]; !ok { - c.plugins[version] = []plugin.Base{} + if _, ok := c.pluginsFromOptions[version]; !ok { + c.pluginsFromOptions[version] = []plugin.Base{} + } + c.pluginsFromOptions[version] = append(c.pluginsFromOptions[version], p) + } + } + return nil + } +} + +// WithDefaultPlugins is an Option that sets the cli's default plugins. +func WithDefaultPlugins(plugins ...plugin.Base) Option { + return func(c *cli) error { + for _, p := range plugins { + for _, version := range p.SupportedProjectVersions() { + if _, ok := c.defaultPluginsFromOptions[version]; !ok { + c.defaultPluginsFromOptions[version] = []plugin.Base{} } - c.plugins[version] = append(c.plugins[version], p) + c.defaultPluginsFromOptions[version] = append(c.defaultPluginsFromOptions[version], p) } } return nil @@ -134,12 +165,20 @@ func WithExtraCommands(cmds ...*cobra.Command) Option { // initialize initializes the cli. func (c *cli) initialize() error { + // Initialize cli with globally-relevant flags or flags that determine + // certain plugin type's configuration. + if err := c.parseBaseFlags(); err != nil { + return err + } + // Configure the project version first for plugin retrieval in command // constructors. projectConfig, err := internalconfig.Read() if os.IsNotExist(err) { c.configured = false - c.projectVersion = c.defaultProjectVersion + if c.projectVersion == "" { + c.projectVersion = c.defaultProjectVersion + } } else if err == nil { c.configured = true c.projectVersion = projectConfig.Version @@ -153,6 +192,40 @@ func (c *cli) initialize() error { return err } + // With or without a config, a user can: + // 1. Not set --plugins + // 2. Set --plugins to a plugin, ex. --plugins=go + // In case 1, either default plugins or the config layout can be used to + // determine which plugin to use. Otherwise, the value passed to --plugins + // is used. Since both layout and a --plugins value are user input, they + // may need to be resolved to known plugin keys. + var filterKeys map[string]string + switch { + case len(c.cliPluginKeys) != 0: + // Filter plugins by keys passed in CLI. + filterKeys = c.cliPluginKeys + case c.configured && !projectConfig.IsV1(): + // All non-v1 configs must have a layout key. This check will help with + // migration. + if projectConfig.Layout == "" { + return fmt.Errorf("config must have a layout value") + } + // Use defaults if no config is found, otherwise infer plugins from the + // config's layout. + name, version := plugin.SplitKey(projectConfig.Layout) + filterKeys = map[string]string{name: version} + default: + // Use the default plugins for this project version. + c.resolvedPlugins = c.defaultPluginsFromOptions[c.projectVersion] + } + if len(filterKeys) != 0 { + plugins := c.pluginsFromOptions[c.projectVersion] + c.resolvedPlugins, err = resolvePluginsByKeys(plugins, filterKeys) + if err != nil { + return err + } + } + c.cmd = c.buildRootCmd() // Add extra commands injected by options. @@ -166,17 +239,49 @@ func (c *cli) initialize() error { } // Write deprecation notices after all commands have been constructed. - if c.projectVersion != "" { - versionedPlugins, err := c.getVersionedPlugins() - if err != nil { - return err + for _, p := range c.resolvedPlugins { + if d, isDeprecated := p.(plugin.Deprecated); isDeprecated { + fmt.Printf(noticeColor, fmt.Sprintf("[Deprecation Notice] %s\n\n", + d.DeprecationWarning())) } - for _, p := range versionedPlugins { - if d, isDeprecated := p.(plugin.Deprecated); isDeprecated { - fmt.Printf(noticeColor, fmt.Sprintf("[Deprecation Notice] %s\n\n", - d.DeprecationWarning())) - } + } + + return nil +} + +// parseBaseFlags parses the command line arguments, looking for flags that +// affect initialization of a cli. An error is returned only if an error +// unrelated to flag parsing occurs. +func (c *cli) parseBaseFlags() error { + // Create a dummy "base" flagset to populate from CLI args. + fs := pflag.NewFlagSet("base", pflag.ExitOnError) + fs.ParseErrorsWhitelist = pflag.ParseErrorsWhitelist{UnknownFlags: true} + + // Set base flags that require pre-parsing to initialize c. + fs.StringVar(&c.projectVersion, projectVersionFlag, c.defaultProjectVersion, "project version") + help := false + fs.BoolVarP(&help, helpFlag, "h", false, "print help") + pluginKeys := []string{} + fs.StringSliceVar(&pluginKeys, pluginNamesFlag, nil, "plugins to run") + + // Parse current CLI args outside of cobra. + err := fs.Parse(os.Args[1:]) + // User needs *generic* help if args are incorrect or --help is set and + // --project-version is not set. Plugin-specific help is given if a + // plugin.Context is updated, which does not require this field. + c.doGenericHelp = err != nil || help && !fs.Lookup(projectVersionFlag).Changed + + // Parse plugin keys into a more manageable data structure (map) and check + // for duplicates. + for _, key := range pluginKeys { + pluginName, pluginVersion := plugin.SplitKey(key) + if pluginName == "" { + return fmt.Errorf("plugin key %q must at least have a name", key) + } + if _, exists := c.cliPluginKeys[pluginName]; exists { + return fmt.Errorf("duplicate plugin name %q", pluginName) } + c.cliPluginKeys[pluginName] = pluginVersion } return nil @@ -192,33 +297,83 @@ func (c cli) validate() error { return fmt.Errorf("failed to validate project version %q: %v", c.projectVersion, err) } + if _, versionFound := c.pluginsFromOptions[c.projectVersion]; !versionFound { + return fmt.Errorf("no plugins for project version %q", c.projectVersion) + } + // If --plugins is not set, no layout exists, and no defaults exist, we + // cannot know which Getter to use for a plugin type. + if !c.configured && len(c.cliPluginKeys) == 0 { + _, versionExists := c.defaultPluginsFromOptions[c.projectVersion] + if !versionExists { + return fmt.Errorf("no default plugins for project version %s", c.projectVersion) + } + } + // Validate plugin versions and name. - for _, versionedPlugins := range c.plugins { - for _, versionedPlugin := range versionedPlugins { - pluginName := versionedPlugin.Name() - pluginVersion := versionedPlugin.Version() + for _, versionedPlugins := range c.pluginsFromOptions { + if err := validatePlugins(versionedPlugins...); err != nil { + return err + } + } + for _, defaultPlugins := range c.defaultPluginsFromOptions { + if err := validatePlugins(defaultPlugins...); err != nil { + return err + } + } + + // Validate plugin keys set in CLI. + for pluginName, pluginVersion := range c.cliPluginKeys { + if err := plugin.ValidateName(pluginName); err != nil { + return fmt.Errorf("failed to validate plugin name %q: %v", pluginName, err) + } + // CLI-set plugins do not have to contain a version. + if pluginVersion != "" { if err := plugin.ValidateVersion(pluginVersion); err != nil { return fmt.Errorf("failed to validate plugin %q version %q: %v", pluginName, pluginVersion, err) } - for _, projectVersion := range versionedPlugin.SupportedProjectVersions() { - if err := validation.ValidateProjectVersion(projectVersion); err != nil { - return fmt.Errorf("failed to validate plugin %q supported project version %q: %v", - pluginName, projectVersion, err) - } - } } } return nil } +// validatePlugins validates the name and versions of a list of plugins. +func validatePlugins(plugins ...plugin.Base) error { + pluginNameSet := make(map[string]struct{}, len(plugins)) + for _, p := range plugins { + pluginName := p.Name() + if err := plugin.ValidateName(pluginName); err != nil { + return fmt.Errorf("failed to validate plugin name %q: %v", pluginName, err) + } + pluginVersion := p.Version() + if err := plugin.ValidateVersion(pluginVersion); err != nil { + return fmt.Errorf("failed to validate plugin %q version %q: %v", + pluginName, pluginVersion, err) + } + for _, projectVersion := range p.SupportedProjectVersions() { + if err := validation.ValidateProjectVersion(projectVersion); err != nil { + return fmt.Errorf("failed to validate plugin %q supported project version %q: %v", + pluginName, projectVersion, err) + } + } + // Check for duplicate plugin names. Names outside of a version can + // conflict because multiple project versions of a plugin may exist. + if _, seen := pluginNameSet[pluginName]; seen { + return fmt.Errorf("two plugins have the same name: %q", pluginName) + } + pluginNameSet[pluginName] = struct{}{} + } + return nil +} + // buildRootCmd returns a root command with a subcommand tree reflecting the // current project's state. func (c cli) buildRootCmd() *cobra.Command { configuredAndV1 := c.configured && c.projectVersion == config.Version1 rootCmd := c.defaultCommand() + rootCmd.PersistentFlags().StringSlice(pluginNamesFlag, nil, "plugins to run") // kubebuilder alpha alphaCmd := c.newAlphaCmd() @@ -248,20 +403,79 @@ func (c cli) buildRootCmd() *cobra.Command { return rootCmd } -// getVersionedPlugins returns all plugins for the project version that c is -// configured with. -func (c cli) getVersionedPlugins() ([]plugin.Base, error) { - if c.projectVersion == "" { - return nil, errors.New("project version not set") +// resolvePluginsByKeys filters versionedPlugins by resolving names from a +// set of plugin keys, returning unambiguously matched plugins. +// A plugin passes the filter if: +// - Name and version are the same. +// - Long or short name is the same, and only one version for that name exists. +// If long or short name is the same, but multiple versions for that name exist, +// resolvePluginsByKeys will not guess which version to use and returns an error. +func resolvePluginsByKeys(versionedPlugins []plugin.Base, cliPluginKeys map[string]string) ([]plugin.Base, error) { + // Find all potential mateches for this plugin's name. + acceptedMatches, maybeMatchesByKey := []plugin.Base{}, map[string][]plugin.Base{} + for pluginName, pluginVersion := range cliPluginKeys { + cliPluginKey := plugin.Key(pluginName, pluginVersion) + // Prevents duplicate versions with the same name from being added to + // maybeMatchesByKey. + foundExactMatch := false + for _, versionedPlugin := range versionedPlugins { + longName := versionedPlugin.Name() + shortName := plugin.GetShortName(longName) + + switch { + // Exact match. + case pluginName == longName && pluginVersion == versionedPlugin.Version(): + acceptedMatches = append(acceptedMatches, versionedPlugin) + delete(maybeMatchesByKey, cliPluginKey) + foundExactMatch = true + // Is at least a name match, and the CLI version wasn't set or was set + // and matches. + case pluginName == longName || pluginName == shortName: + if pluginVersion == "" || pluginVersion == versionedPlugin.Version() { + maybeMatchesByKey[cliPluginKey] = append(maybeMatchesByKey[cliPluginKey], versionedPlugin) + } + } + // No more plugins need to be checked for cliPluginKey. + if foundExactMatch { + break + } + } + // No possible or exact plugin match was found. + _, foundMaybeMatch := maybeMatchesByKey[cliPluginKey] + if !foundExactMatch && !foundMaybeMatch { + return nil, fmt.Errorf("no plugins for key %q were found", cliPluginKey) + } + } + + // No ambiguously keyed plugins. + if len(maybeMatchesByKey) == 0 { + return acceptedMatches, nil + } + + errMsgs := []string{} + for key, maybeMatches := range maybeMatchesByKey { + if len(maybeMatches) == 1 { + // Only one plugin for a CLI key, which means there's only one version + // for that plugin and either its short or long name matched the CLI name. + acceptedMatches = append(acceptedMatches, maybeMatches...) + } else { + // Multiple possible plugins the user could be specifying, return an + // error with ambiguous plugin info. + pluginKeys := []string{} + for _, p := range maybeMatches { + pluginKeys = append(pluginKeys, plugin.KeyFor(p)) + } + errMsgs = append(errMsgs, fmt.Sprintf("%q: %+q", key, pluginKeys)) + } } - versionedPlugins, versionFound := c.plugins[c.projectVersion] - if !versionFound { - return nil, fmt.Errorf("unknown project version %q", c.projectVersion) + + if len(errMsgs) == 0 { + return acceptedMatches, nil } - return versionedPlugins, nil + return nil, fmt.Errorf("ambiguous plugin keys, possible matches: %s", strings.Join(errMsgs, ", ")) } -// defaultCommand results the root command without its subcommands. +// defaultCommand returns the root command without its subcommands. func (c cli) defaultCommand() *cobra.Command { return &cobra.Command{ Use: c.commandName, diff --git a/pkg/cli/init.go b/pkg/cli/init.go index 026e41495f6..3ab34022e28 100644 --- a/pkg/cli/init.go +++ b/pkg/cli/init.go @@ -24,7 +24,6 @@ import ( "strings" "github.com/spf13/cobra" - "github.com/spf13/pflag" "sigs.k8s.io/kubebuilder/internal/config" "sigs.k8s.io/kubebuilder/pkg/plugin" @@ -45,13 +44,8 @@ func (c *cli) newInitCmd() *cobra.Command { cmd.Flags().String("project-version", c.defaultProjectVersion, fmt.Sprintf("project version, possible values: (%s)", strings.Join(c.getAvailableProjectVersions(), ", "))) - // Pre-parse the project version and help flags so that we can - // dynamically bind to a plugin's init implementation (or not). - var isHelpOnly bool - c.projectVersion, isHelpOnly = c.getBaseFlags() - // If only the help flag was set, return the command as is. - if isHelpOnly { + if c.doGenericHelp { return cmd } @@ -85,7 +79,7 @@ func (c cli) getInitHelpExamples() string { } func (c cli) getAvailableProjectVersions() (projectVersions []string) { - for version, versionedPlugins := range c.plugins { + for version, versionedPlugins := range c.pluginsFromOptions { for _, p := range versionedPlugins { // There will only be one init plugin per version. if _, isInit := p.(plugin.Init); !isInit { @@ -100,37 +94,10 @@ func (c cli) getAvailableProjectVersions() (projectVersions []string) { return projectVersions } -// getBaseFlags parses the command line arguments, looking for --project-version -// and help. If an error occurs or only --help is set, getBaseFlags returns an -// empty string and true. Otherwise, getBaseFlags returns the project version -// and false. -func (c cli) getBaseFlags() (string, bool) { - fs := pflag.NewFlagSet("base", pflag.ExitOnError) - fs.ParseErrorsWhitelist = pflag.ParseErrorsWhitelist{UnknownFlags: true} - - var ( - projectVersion string - help bool - ) - fs.StringVar(&projectVersion, "project-version", c.defaultProjectVersion, "project version") - fs.BoolVarP(&help, "help", "h", false, "print help") - - err := fs.Parse(os.Args[1:]) - doHelp := err != nil || help && !fs.Lookup("project-version").Changed - if doHelp { - return "", true - } - return projectVersion, false -} - func (c cli) bindInit(ctx plugin.Context, cmd *cobra.Command) { - versionedPlugins, err := c.getVersionedPlugins() - if err != nil { - log.Fatal(err) - } var getter plugin.InitPluginGetter var hasGetter bool - for _, p := range versionedPlugins { + for _, p := range c.resolvedPlugins { tmpGetter, isGetter := p.(plugin.InitPluginGetter) if isGetter { if hasGetter { @@ -142,7 +109,7 @@ func (c cli) bindInit(ctx plugin.Context, cmd *cobra.Command) { } } if !hasGetter { - log.Fatalf("project version %q does not support a project initialization plugin", + log.Fatalf("project version %q does not support an initialization plugin", c.projectVersion) } diff --git a/pkg/cli/webhook.go b/pkg/cli/webhook.go index f52aa14f149..47ea906a383 100644 --- a/pkg/cli/webhook.go +++ b/pkg/cli/webhook.go @@ -55,14 +55,9 @@ func (c cli) newWebhookContext() plugin.Context { } func (c cli) bindCreateWebhook(ctx plugin.Context, cmd *cobra.Command) { - versionedPlugins, err := c.getVersionedPlugins() - if err != nil { - cmdErr(cmd, err) - return - } var getter plugin.CreateWebhookPluginGetter var hasGetter bool - for _, p := range versionedPlugins { + for _, p := range c.resolvedPlugins { tmpGetter, isGetter := p.(plugin.CreateWebhookPluginGetter) if isGetter { if hasGetter { diff --git a/pkg/model/config/config.go b/pkg/model/config/config.go index 2623c96a687..0736d9be06d 100644 --- a/pkg/model/config/config.go +++ b/pkg/model/config/config.go @@ -43,6 +43,9 @@ type Config struct { // Multigroup tracks if the project has more than one group MultiGroup bool `json:"multigroup,omitempty"` + + // Layout contains a key specifying which plugin created a project. + Layout string `json:"layout,omitempty"` } // IsV1 returns true if it is a v1 project diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index ab0ae5ad878..397b1a6d4ee 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -25,11 +25,11 @@ import ( "sigs.k8s.io/kubebuilder/pkg/model/config" ) +const DefaultNameQualifier = ".kubebuilder.io" + type Base interface { // Name returns a DNS1123 label string defining the plugin type. // For example, Kubebuilder's main plugin would return "go". - // - // TODO: fully-qualified automatic append and comparison. Name() string // Version returns the plugin's semantic version, ex. "v1.2.3". // @@ -40,9 +40,32 @@ type Base interface { SupportedProjectVersions() []string } -// Key returns a Base plugin's unique identifying string. -func Key(p Base) string { - return path.Join(p.Name(), "v"+strings.TrimLeft(p.Version(), "v")) +// Key returns a unique identifying string for a plugin's name and version. +func Key(name, version string) string { + if version == "" { + return name + } + return path.Join(name, "v"+strings.TrimLeft(version, "v")) +} + +// KeyFor returns a Base plugin's unique identifying string. +func KeyFor(p Base) string { + return Key(p.Name(), p.Version()) +} + +// SplitKey returns a name and version for a plugin key. +func SplitKey(key string) (string, string) { + if !strings.Contains(key, "/") { + return key, "" + } + keyParts := strings.SplitN(key, "/", 2) + return keyParts[0], keyParts[1] +} + +// GetShortName returns plugin's short name (name before domain) if name +// is fully qualified (has a domain suffix), otherwise GetShortName returns name. +func GetShortName(name string) string { + return strings.SplitN(name, ".", 2)[0] } type Deprecated interface { diff --git a/pkg/plugin/v1/init.go b/pkg/plugin/v1/init.go index bd2da896865..b3674425ef4 100644 --- a/pkg/plugin/v1/init.go +++ b/pkg/plugin/v1/init.go @@ -35,7 +35,7 @@ import ( "sigs.k8s.io/kubebuilder/pkg/scaffold" ) -type initPlugin struct { // nolint:maligned +type initPlugin struct { config *config.Config // boilerplate options diff --git a/pkg/plugin/v1/plugin.go b/pkg/plugin/v1/plugin.go index e8999256a8c..1286f289b0c 100644 --- a/pkg/plugin/v1/plugin.go +++ b/pkg/plugin/v1/plugin.go @@ -20,6 +20,16 @@ import ( "sigs.k8s.io/kubebuilder/pkg/plugin" ) +const ( + pluginName = "go" + plugin.DefaultNameQualifier + pluginVersion = "v1.0.0" + + deprecationWarning = `The v1 projects are deprecated and will not be supported beyond Feb 1, 2020. +See how to upgrade your project to v2: https://book.kubebuilder.io/migration/guide.html` +) + +var supportedProjectVersions = []string{"1"} + var ( _ plugin.Base = Plugin{} _ plugin.InitPluginGetter = Plugin{} @@ -34,31 +44,10 @@ type Plugin struct { createWebhookPlugin } -func (Plugin) Name() string { - return "go" -} - -func (Plugin) Version() string { - return "v1.0.0" -} - -func (Plugin) SupportedProjectVersions() []string { - return []string{"1"} -} - -func (p Plugin) GetInitPlugin() plugin.Init { - return &p.initPlugin -} - -func (p Plugin) GetCreateAPIPlugin() plugin.CreateAPI { - return &p.createAPIPlugin -} - -func (p Plugin) GetCreateWebhookPlugin() plugin.CreateWebhook { - return &p.createWebhookPlugin -} - -func (Plugin) DeprecationWarning() string { - return `The v1 projects are deprecated and will not be supported beyond Feb 1, 2020. -See how to upgrade your project to v2: https://book.kubebuilder.io/migration/guide.html` -} +func (Plugin) Name() string { return pluginName } +func (Plugin) Version() string { return pluginVersion } +func (Plugin) SupportedProjectVersions() []string { return supportedProjectVersions } +func (p Plugin) GetInitPlugin() plugin.Init { return &p.initPlugin } +func (p Plugin) GetCreateAPIPlugin() plugin.CreateAPI { return &p.createAPIPlugin } +func (p Plugin) GetCreateWebhookPlugin() plugin.CreateWebhook { return &p.createWebhookPlugin } +func (Plugin) DeprecationWarning() string { return deprecationWarning } diff --git a/pkg/plugin/v2/init.go b/pkg/plugin/v2/init.go index 7f6086074bf..082e2ada0a0 100644 --- a/pkg/plugin/v2/init.go +++ b/pkg/plugin/v2/init.go @@ -33,7 +33,7 @@ import ( "sigs.k8s.io/kubebuilder/pkg/scaffold" ) -type initPlugin struct { // nolint:maligned +type initPlugin struct { config *config.Config // boilerplate options @@ -107,6 +107,8 @@ func (p *initPlugin) BindFlags(fs *pflag.FlagSet) { } func (p *initPlugin) InjectConfig(c *config.Config) { + // v2 project configs get a 'layout' value. + c.Layout = plugin.KeyFor(Plugin{}) p.config = c } diff --git a/pkg/plugin/v2/plugin.go b/pkg/plugin/v2/plugin.go index 8589de4edd4..d66c2c621e6 100644 --- a/pkg/plugin/v2/plugin.go +++ b/pkg/plugin/v2/plugin.go @@ -20,6 +20,13 @@ import ( "sigs.k8s.io/kubebuilder/pkg/plugin" ) +const ( + pluginName = "go" + plugin.DefaultNameQualifier + pluginVersion = "v2.0.0" +) + +var supportedProjectVersions = []string{"2"} + var ( _ plugin.Base = Plugin{} _ plugin.InitPluginGetter = Plugin{} @@ -33,26 +40,9 @@ type Plugin struct { createWebhookPlugin } -func (Plugin) Name() string { - return "go" -} - -func (Plugin) Version() string { - return "v2.0.0" -} - -func (Plugin) SupportedProjectVersions() []string { - return []string{"2"} -} - -func (p Plugin) GetInitPlugin() plugin.Init { - return &p.initPlugin -} - -func (p Plugin) GetCreateAPIPlugin() plugin.CreateAPI { - return &p.createAPIPlugin -} - -func (p Plugin) GetCreateWebhookPlugin() plugin.CreateWebhook { - return &p.createWebhookPlugin -} +func (Plugin) Name() string { return pluginName } +func (Plugin) Version() string { return pluginVersion } +func (Plugin) SupportedProjectVersions() []string { return supportedProjectVersions } +func (p Plugin) GetInitPlugin() plugin.Init { return &p.initPlugin } +func (p Plugin) GetCreateAPIPlugin() plugin.CreateAPI { return &p.createAPIPlugin } +func (p Plugin) GetCreateWebhookPlugin() plugin.CreateWebhook { return &p.createWebhookPlugin } diff --git a/pkg/plugin/validation.go b/pkg/plugin/validation.go index 3f1a5948cd1..8503ca88469 100644 --- a/pkg/plugin/validation.go +++ b/pkg/plugin/validation.go @@ -21,6 +21,8 @@ import ( "fmt" "github.com/blang/semver" + + "sigs.k8s.io/kubebuilder/pkg/internal/validation" ) // ValidateVersion ensures version adheres to the plugin version format, @@ -36,3 +38,11 @@ func ValidateVersion(version string) error { } return nil } + +// ValidateName ensures name is a valid DNS 1123 subdomain. +func ValidateName(name string) error { + if errs := validation.IsDNS1123Subdomain(name); len(errs) != 0 { + return fmt.Errorf("plugin name %q is invalid: %v", name, errs) + } + return nil +} diff --git a/testdata/project-v2-addon/PROJECT b/testdata/project-v2-addon/PROJECT index 77f34e78733..0c25e2e24a8 100644 --- a/testdata/project-v2-addon/PROJECT +++ b/testdata/project-v2-addon/PROJECT @@ -1,4 +1,5 @@ domain: testproject.org +layout: go.kubebuilder.io/v2.0.0 repo: sigs.k8s.io/kubebuilder/testdata/project-v2-addon resources: - group: crew diff --git a/testdata/project-v2-multigroup/PROJECT b/testdata/project-v2-multigroup/PROJECT index eab6e3e2b0a..94a91056e92 100644 --- a/testdata/project-v2-multigroup/PROJECT +++ b/testdata/project-v2-multigroup/PROJECT @@ -1,4 +1,5 @@ domain: testproject.org +layout: go.kubebuilder.io/v2.0.0 multigroup: true repo: sigs.k8s.io/kubebuilder/testdata/project-v2-multigroup resources: diff --git a/testdata/project-v2/PROJECT b/testdata/project-v2/PROJECT index 36aacd80d5b..ed51cfbb71b 100644 --- a/testdata/project-v2/PROJECT +++ b/testdata/project-v2/PROJECT @@ -1,4 +1,5 @@ domain: testproject.org +layout: go.kubebuilder.io/v2.0.0 repo: sigs.k8s.io/kubebuilder/testdata/project-v2 resources: - group: crew