diff --git a/Makefile b/Makefile index 0f0e1c8cb34..f064d91b24f 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,7 @@ test-unit: ## Run the unit tests .PHONY: test-coverage test-coverage: ## Run unit tests creating the output to report coverage - rm -rf *.out # Remove all coverage files if exists - go test -race -failfast -tags=integration -coverprofile=coverage-all.out -coverpkg="./pkg/cli/...,./pkg/config/...,./pkg/internal/...,./pkg/model/...,./pkg/plugin/...,./pkg/plugins/golang,./pkg/plugins/internal/..." ./pkg/... + go test -race -failfast -tags=integration -coverprofile=coverage-all.out -coverpkg="./pkg/cli/...,./pkg/config/...,./pkg/internal/...,./pkg/machinery/...,./pkg/model/...,./pkg/plugin/...,./pkg/plugins/golang" ./pkg/... .PHONY: test-integration test-integration: ## Run the integration tests diff --git a/cmd/main.go b/cmd/main.go index a91a49a7821..ef8393264db 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -22,6 +22,7 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/cli" cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" + declarativev1 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/declarative/v1" pluginv2 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2" pluginv3 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3" ) @@ -34,6 +35,7 @@ func main() { cli.WithPlugins( &pluginv2.Plugin{}, &pluginv3.Plugin{}, + &declarativev1.Plugin{}, ), cli.WithDefaultPlugins(cfgv2.Version, &pluginv2.Plugin{}), cli.WithDefaultPlugins(cfgv3.Version, &pluginv3.Plugin{}), diff --git a/pkg/cli/api.go b/pkg/cli/api.go index c858784962f..3f8bc2a033f 100644 --- a/pkg/cli/api.go +++ b/pkg/cli/api.go @@ -14,80 +14,58 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cli // nolint:dupl +package cli //nolint:dupl import ( "fmt" "github.com/spf13/cobra" - "sigs.k8s.io/kubebuilder/v3/pkg/cli/internal/config" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) +const apiErrorMsg = "failed to create API" + func (c cli) newCreateAPICmd() *cobra.Command { - ctx := c.newAPIContext() cmd := &cobra.Command{ - Use: "api", - Short: "Scaffold a Kubernetes API", - Long: ctx.Description, - Example: ctx.Examples, + Use: "api", + Short: "Scaffold a Kubernetes API", + Long: `Scaffold a Kubernetes API. +`, RunE: errCmdFunc( fmt.Errorf("api subcommand requires an existing project"), ), } - // Lookup the plugin for projectVersion and bind it to the command. - c.bindCreateAPI(ctx, cmd) - return cmd -} - -func (c cli) newAPIContext() plugin.Context { - return plugin.Context{ - CommandName: c.commandName, - Description: `Scaffold a Kubernetes API. -`, - } -} - -// nolint:dupl -func (c cli) bindCreateAPI(ctx plugin.Context, cmd *cobra.Command) { + // In case no plugin was resolved, instead of failing the construction of the CLI, fail the execution of + // this subcommand. This allows the use of subcommands that do not require resolved plugins like help. if len(c.resolvedPlugins) == 0 { - cmdErr(cmd, fmt.Errorf(noPluginError)) - return + cmdErr(cmd, noResolvedPluginError{}) + return cmd } - var createAPIPlugin plugin.CreateAPI - for _, p := range c.resolvedPlugins { - tmpPlugin, isValid := p.(plugin.CreateAPI) - if isValid { - if createAPIPlugin != nil { - err := fmt.Errorf("duplicate API creation plugins (%s, %s), use a more specific plugin key", - plugin.KeyFor(createAPIPlugin), plugin.KeyFor(p)) - cmdErr(cmd, err) - return - } - createAPIPlugin = tmpPlugin - } - } + // Obtain the plugin keys and subcommands from the plugins that implement plugin.CreateAPI. + pluginKeys, subcommands := c.filterSubcommands( + func(p plugin.Plugin) bool { + _, isValid := p.(plugin.CreateAPI) + return isValid + }, + func(p plugin.Plugin) plugin.Subcommand { + return p.(plugin.CreateAPI).GetCreateAPISubcommand() + }, + ) - if createAPIPlugin == nil { - cmdErr(cmd, fmt.Errorf("resolved plugins do not provide an API creation plugin: %v", c.pluginKeys)) - return + // Verify that there is at least one remaining plugin. + if len(subcommands) == 0 { + cmdErr(cmd, noAvailablePluginError{"API creation"}) + return cmd } - cfg, err := config.LoadInitialized() - if err != nil { - cmdErr(cmd, err) - return - } + // Initialization methods. + options := c.initializationMethods(cmd, subcommands) + + // Execution methods. + cmd.PreRunE, cmd.RunE, cmd.PostRunE = c.executionMethodsFuncs(pluginKeys, subcommands, options, apiErrorMsg) - subcommand := createAPIPlugin.GetCreateAPISubcommand() - subcommand.InjectConfig(cfg.Config) - subcommand.BindFlags(cmd.Flags()) - subcommand.UpdateContext(&ctx) - cmd.Long = ctx.Description - cmd.Example = ctx.Examples - cmd.RunE = runECmdFunc(cfg, subcommand, - fmt.Sprintf("failed to create API with %q", plugin.KeyFor(createAPIPlugin))) + return cmd } diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 8c12384676b..eeda6863869 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -17,15 +17,17 @@ limitations under the License. package cli import ( + "errors" "fmt" "os" "strings" + "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/spf13/pflag" - internalconfig "sigs.k8s.io/kubebuilder/v3/pkg/cli/internal/config" "sigs.k8s.io/kubebuilder/v3/pkg/config" + yamlstore "sigs.k8s.io/kubebuilder/v3/pkg/config/store/yaml" cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) @@ -36,8 +38,6 @@ const ( projectVersionFlag = "project-version" pluginsFlag = "plugins" - - noPluginError = "invalid config file please verify that the version and layout fields are set and valid" ) // equalStringSlice checks if two string slices are equal. @@ -96,6 +96,9 @@ type cli struct { //nolint:maligned // Root command. cmd *cobra.Command + + // Underlying fs + fs afero.Fs } // New creates a new cli instance. @@ -134,6 +137,7 @@ func newCLI(opts ...Option) (*cli, error) { defaultProjectVersion: cfgv3.Version, defaultPlugins: make(map[config.Version][]string), plugins: make(map[string]plugin.Plugin), + fs: afero.NewOsFs(), } // Apply provided options. @@ -191,18 +195,19 @@ func (c *cli) getInfoFromFlags() (string, []string, error) { } // getInfoFromConfigFile obtains the project version and plugin keys from the project config file. -func getInfoFromConfigFile() (config.Version, []string, error) { +func (c cli) getInfoFromConfigFile() (config.Version, []string, error) { // Read the project configuration file - projectConfig, err := internalconfig.Read() + cfg := yamlstore.New(c.fs) + err := cfg.Load() switch { case err == nil: - case os.IsNotExist(err): + case errors.Is(err, os.ErrNotExist): return config.Version{}, nil, nil default: return config.Version{}, nil, err } - return getInfoFromConfig(projectConfig) + return getInfoFromConfig(cfg.Config()) } // getInfoFromConfig obtains the project version and plugin keys from the project config. @@ -297,7 +302,7 @@ func (c *cli) getInfo() error { return err } // Get project version and plugin info from project configuration file - cfgProjectVersion, cfgPlugins, _ := getInfoFromConfigFile() + cfgProjectVersion, cfgPlugins, _ := c.getInfoFromConfigFile() // We discard the error because not being able to read a project configuration file // is not fatal for some commands. The ones that require it need to check its existence. @@ -473,6 +478,13 @@ func (c cli) printDeprecationWarnings() { } } +// metadata returns CLI's metadata. +func (c cli) metadata() plugin.CLIMetadata { + return plugin.CLIMetadata{ + CommandName: c.commandName, + } +} + // Run implements CLI.Run. func (c cli) Run() error { return c.cmd.Execute() diff --git a/pkg/cli/cli_test.go b/pkg/cli/cli_test.go index d9b7000e439..a7fe02cfd02 100644 --- a/pkg/cli/cli_test.go +++ b/pkg/cli/cli_test.go @@ -23,6 +23,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/spf13/afero" "github.com/spf13/cobra" "sigs.k8s.io/kubebuilder/v3/pkg/config" @@ -585,6 +586,7 @@ var _ = Describe("CLI", func() { defaultPlugins: map[config.Version][]string{ projectVersion: pluginKeys, }, + fs: afero.NewMemMapFs(), } c.cmd = c.newRootCmd() Expect(c.getInfo()).To(Succeed()) diff --git a/pkg/cli/cmd_helpers.go b/pkg/cli/cmd_helpers.go index a7b51d6ee6f..00d8622b681 100644 --- a/pkg/cli/cmd_helpers.go +++ b/pkg/cli/cmd_helpers.go @@ -17,14 +17,37 @@ limitations under the License. package cli import ( + "errors" "fmt" + "os" + "github.com/spf13/afero" "github.com/spf13/cobra" - "sigs.k8s.io/kubebuilder/v3/pkg/cli/internal/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config/store" + yamlstore "sigs.k8s.io/kubebuilder/v3/pkg/config/store/yaml" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) +// noResolvedPluginError is returned by subcommands that require a plugin when none was resolved. +type noResolvedPluginError struct{} + +// Error implements error interface. +func (e noResolvedPluginError) Error() string { + return "no resolved plugin, please verify the project version and plugins specified in flags or configuration file" +} + +// noAvailablePluginError is returned by subcommands that require a plugin when none of their specific type was found. +type noAvailablePluginError struct { + subcommand string +} + +// Error implements error interface. +func (e noAvailablePluginError) Error() string { + return fmt.Sprintf("resolved plugins do not provide any %s subcommand", e.subcommand) +} + // cmdErr updates a cobra command to output error information when executed // or used with the help flag. func cmdErr(cmd *cobra.Command, err error) { @@ -32,12 +55,6 @@ func cmdErr(cmd *cobra.Command, err error) { cmd.RunE = errCmdFunc(err) } -// cmdErrNoHelp calls cmdErr(cmd, err) then turns cmd's usage off. -func cmdErrNoHelp(cmd *cobra.Command, err error) { - cmdErr(cmd, err) - cmd.SilenceUsage = true -} - // errCmdFunc returns a cobra RunE function that returns the provided error func errCmdFunc(err error) func(*cobra.Command, []string) error { return func(*cobra.Command, []string) error { @@ -45,17 +62,209 @@ func errCmdFunc(err error) func(*cobra.Command, []string) error { } } -// runECmdFunc returns a cobra RunE function that runs subcommand and saves the -// config, which may have been modified by subcommand. -func runECmdFunc( - c *config.Config, - subcommand plugin.Subcommand, // nolint:interfacer +// filterSubcommands returns a list of plugin keys and subcommands from a filtered list of resolved plugins. +func (c cli) filterSubcommands( + filter func(plugin.Plugin) bool, + extract func(plugin.Plugin) plugin.Subcommand, +) ([]string, []plugin.Subcommand) { + pluginKeys := make([]string, 0, len(c.resolvedPlugins)) + subcommands := make([]plugin.Subcommand, 0, len(c.resolvedPlugins)) + for _, p := range c.resolvedPlugins { + if filter(p) { + pluginKeys = append(pluginKeys, plugin.KeyFor(p)) + subcommands = append(subcommands, extract(p)) + } + } + return pluginKeys, subcommands +} + +// initializationMethods +func (c cli) initializationMethods(cmd *cobra.Command, subcommands []plugin.Subcommand) *ResourceOptions { + // Update metadata method. + meta := plugin.SubcommandMetadata{ + Description: cmd.Long, + Examples: cmd.Example, + } + for _, subcommand := range subcommands { + if subcmd, updatesMetadata := subcommand.(plugin.UpdatesMetadata); updatesMetadata { + subcmd.UpdateMetadata(c.metadata(), &meta) + } + } + cmd.Long = meta.Description + cmd.Example = meta.Examples + + // Before binding specific plugin flags, bind common ones + requiresResource := false + for _, subcommand := range subcommands { + if _, requiresResource = subcommand.(plugin.RequiresResource); requiresResource { + break + } + } + var options *ResourceOptions + if requiresResource { + options = bindResourceFlags(cmd.Flags()) + } + + // Bind flags method. + for _, subcommand := range subcommands { + if subcmd, hasFlags := subcommand.(plugin.HasFlags); hasFlags { + subcmd.BindFlags(cmd.Flags()) + } + } + + return options +} + +// executionMethodsFuncs returns cobra RunE functions for PreRunE, RunE, PostRunE cobra hooks. +func (c cli) executionMethodsFuncs( + pluginKeys []string, + subcommands []plugin.Subcommand, + options *ResourceOptions, + msg string, +) ( + func(*cobra.Command, []string) error, + func(*cobra.Command, []string) error, + func(*cobra.Command, []string) error, +) { + cfg := yamlstore.New(c.fs) + return executionMethodsPreRunEFunc(pluginKeys, subcommands, cfg, options, c.fs, msg), + executionMethodsRunEFunc(pluginKeys, subcommands, c.fs, msg), + executionMethodsPostRunEFunc(pluginKeys, subcommands, cfg, msg) +} + +// executionMethodsPreRunEFunc returns a cobra RunE function that loads the configuration +// and executes inject config, inject resource and pre-scaffold methods. +func executionMethodsPreRunEFunc( + pluginKeys []string, + subcommands []plugin.Subcommand, + cfg store.Store, + options *ResourceOptions, + fs afero.Fs, msg string, ) func(*cobra.Command, []string) error { return func(*cobra.Command, []string) error { - if err := subcommand.Run(); err != nil { - return fmt.Errorf("%s: %v", msg, err) + if err := cfg.Load(); os.IsNotExist(err) { + return fmt.Errorf("%s: unable to find configuration file, project must be initialized", msg) + } else if err != nil { + return fmt.Errorf("%s: unable to load configuration file: %w", msg, err) } - return c.Save() + + var res *resource.Resource + if options != nil { + options.Domain = cfg.Config().GetDomain() // TODO: offer a flag instead of hard-coding project-wide domain + if err := options.Validate(); err != nil { + return fmt.Errorf("%s: unable to create resource: %w", msg, err) + } + res = options.NewResource() + if err := res.Validate(); err != nil { + return fmt.Errorf("%s: created invalid resource: %w", msg, err) + } + } + + // Inject config method. + for i, subcommand := range subcommands { + if subcmd, requiresConfig := subcommand.(plugin.RequiresConfig); requiresConfig { + if err := subcmd.InjectConfig(cfg.Config()); err != nil { + var exitError plugin.ExitError + if errors.As(err, &exitError) { + fmt.Printf("skipping %q: %s\n", pluginKeys[i], exitError.Reason) + subcommands = append(subcommands[:i], subcommands[i+1:]...) + } else { + return fmt.Errorf("%s: unable to inject the configuration to %q: %w", msg, pluginKeys[i], err) + } + } + } + } + + // Inject resource method. + for i, subcommand := range subcommands { + if subcmd, requiresResource := subcommand.(plugin.RequiresResource); requiresResource { + if err := subcmd.InjectResource(res); err != nil { + var exitError plugin.ExitError + if errors.As(err, &exitError) { + fmt.Printf("skipping %q: %s\n", pluginKeys[i], exitError.Reason) + subcommands = append(subcommands[:i], subcommands[i+1:]...) + } else { + return fmt.Errorf("%s: unable to inject the resource to %q: %w", msg, pluginKeys[i], err) + } + } + } + } + + // Pre-scaffold method. + for i, subcommand := range subcommands { + if subcmd, hasPreScaffold := subcommand.(plugin.HasPreScaffold); hasPreScaffold { + if err := subcmd.PreScaffold(fs); err != nil { + var exitError plugin.ExitError + if errors.As(err, &exitError) { + fmt.Printf("skipping %q: %s\n", pluginKeys[i], exitError.Reason) + subcommands = append(subcommands[:i], subcommands[i+1:]...) + } else { + return fmt.Errorf("%s: unable to run pre-scaffold tasks of %q: %w", msg, pluginKeys[i], err) + } + } + } + } + + return nil + } +} + +// executionMethodsRunEFunc returns a cobra RunE function that executes the scaffold method. +func executionMethodsRunEFunc( + pluginKeys []string, + subcommands []plugin.Subcommand, + fs afero.Fs, + msg string, +) func(*cobra.Command, []string) error { + return func(*cobra.Command, []string) error { + // Scaffold method. + for i, subcommand := range subcommands { + if err := subcommand.Scaffold(fs); err != nil { + var exitError plugin.ExitError + if errors.As(err, &exitError) { + fmt.Printf("skipping %q: %s\n", pluginKeys[i], exitError.Reason) + subcommands = append(subcommands[:i], subcommands[i+1:]...) + } else { + return fmt.Errorf("%s: unable to scaffold with %q: %v", msg, pluginKeys[i], err) + } + } + } + + return nil + } +} + +// executionMethodsPostRunEFunc returns a cobra RunE function that executes the post-scaffold method +// and saves the configuration. +func executionMethodsPostRunEFunc( + pluginKeys []string, + subcommands []plugin.Subcommand, + cfg store.Store, + msg string, +) func(*cobra.Command, []string) error { + return func(*cobra.Command, []string) error { + err := cfg.Save() + if err != nil { + return fmt.Errorf("%s: unable to save configuration file: %w", msg, err) + } + + // Post-scaffold method. + for i, subcommand := range subcommands { + if subcmd, hasPostScaffold := subcommand.(plugin.HasPostScaffold); hasPostScaffold { + if err := subcmd.PostScaffold(); err != nil { + var exitError plugin.ExitError + if errors.As(err, &exitError) { + fmt.Printf("skipping %q: %s\n", pluginKeys[i], exitError.Reason) + subcommands = append(subcommands[:i], subcommands[i+1:]...) + } else { + return fmt.Errorf("%s: unable to run post-scaffold tasks of %q: %w", msg, pluginKeys[i], err) + } + } + } + + } + + return nil } } diff --git a/pkg/cli/edit.go b/pkg/cli/edit.go index a5908a08364..108f7fe5a3b 100644 --- a/pkg/cli/edit.go +++ b/pkg/cli/edit.go @@ -14,80 +14,58 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cli // nolint:dupl +package cli //nolint:dupl import ( "fmt" "github.com/spf13/cobra" - "sigs.k8s.io/kubebuilder/v3/pkg/cli/internal/config" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) +const editErrorMsg = "failed to edit project" + func (c cli) newEditCmd() *cobra.Command { - ctx := c.newEditContext() cmd := &cobra.Command{ - Use: "edit", - Short: "This command will edit the project configuration", - Long: ctx.Description, - Example: ctx.Examples, + Use: "edit", + Short: "This command will edit the project configuration", + Long: `Edit the project configuration. +`, RunE: errCmdFunc( fmt.Errorf("project must be initialized"), ), } - // Lookup the plugin for projectVersion and bind it to the command. - c.bindEdit(ctx, cmd) - return cmd -} - -func (c cli) newEditContext() plugin.Context { - return plugin.Context{ - CommandName: c.commandName, - Description: `Edit the project configuration. -`, - } -} - -func (c cli) bindEdit(ctx plugin.Context, cmd *cobra.Command) { + // In case no plugin was resolved, instead of failing the construction of the CLI, fail the execution of + // this subcommand. This allows the use of subcommands that do not require resolved plugins like help. if len(c.resolvedPlugins) == 0 { - cmdErr(cmd, fmt.Errorf(noPluginError)) - return + cmdErr(cmd, noResolvedPluginError{}) + return cmd } - var editPlugin plugin.Edit - for _, p := range c.resolvedPlugins { - tmpPlugin, isValid := p.(plugin.Edit) - if isValid { - if editPlugin != nil { - err := fmt.Errorf( - "duplicate edit project plugins (%s, %s), use a more specific plugin key", - plugin.KeyFor(editPlugin), plugin.KeyFor(p)) - cmdErr(cmd, err) - return - } - editPlugin = tmpPlugin - } - } + // Obtain the plugin keys and subcommands from the plugins that implement plugin.Edit. + pluginKeys, subcommands := c.filterSubcommands( + func(p plugin.Plugin) bool { + _, isValid := p.(plugin.Edit) + return isValid + }, + func(p plugin.Plugin) plugin.Subcommand { + return p.(plugin.Edit).GetEditSubcommand() + }, + ) - if editPlugin == nil { - cmdErr(cmd, fmt.Errorf("resolved plugins do not provide a project edit plugin: %v", c.pluginKeys)) - return + // Verify that there is at least one remaining plugin. + if len(subcommands) == 0 { + cmdErr(cmd, noAvailablePluginError{"edit project"}) + return cmd } - cfg, err := config.LoadInitialized() - if err != nil { - cmdErr(cmd, err) - return - } + // Initialization methods. + options := c.initializationMethods(cmd, subcommands) + + // Execution methods. + cmd.PreRunE, cmd.RunE, cmd.PostRunE = c.executionMethodsFuncs(pluginKeys, subcommands, options, editErrorMsg) - subcommand := editPlugin.GetEditSubcommand() - subcommand.InjectConfig(cfg.Config) - subcommand.BindFlags(cmd.Flags()) - subcommand.UpdateContext(&ctx) - cmd.Long = ctx.Description - cmd.Example = ctx.Examples - cmd.RunE = runECmdFunc(cfg, subcommand, - fmt.Sprintf("failed to edit project with %q", plugin.KeyFor(editPlugin))) + return cmd } diff --git a/pkg/cli/init.go b/pkg/cli/init.go index 92997491bc2..f7f38ce8564 100644 --- a/pkg/cli/init.go +++ b/pkg/cli/init.go @@ -17,8 +17,8 @@ limitations under the License. package cli import ( + "errors" "fmt" - "log" "os" "sort" "strconv" @@ -26,19 +26,23 @@ import ( "github.com/spf13/cobra" - internalconfig "sigs.k8s.io/kubebuilder/v3/pkg/cli/internal/config" "sigs.k8s.io/kubebuilder/v3/pkg/config" + yamlstore "sigs.k8s.io/kubebuilder/v3/pkg/config/store/yaml" cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) +const initErrorMsg = "failed to initialize project" + func (c cli) newInitCmd() *cobra.Command { - ctx := c.newInitContext() cmd := &cobra.Command{ - Use: "init", - Short: "Initialize a new project", - Long: ctx.Description, - Example: ctx.Examples, + Use: "init", + Short: "Initialize a new project", + Long: `Initialize a new project. + +For further help about a specific project version, set --project-version. +`, + Example: c.getInitHelpExamples(), Run: func(cmd *cobra.Command, args []string) {}, } @@ -53,20 +57,90 @@ func (c cli) newInitCmd() *cobra.Command { fmt.Sprintf("Available plugins: (%s)", strings.Join(c.getAvailablePlugins(), ", "))) } - // Lookup the plugin for projectVersion and bind it to the command. - c.bindInit(ctx, cmd) - return cmd -} + // In case no plugin was resolved, instead of failing the construction of the CLI, fail the execution of + // this subcommand. This allows the use of subcommands that do not require resolved plugins like help. + if len(c.resolvedPlugins) == 0 { + cmdErr(cmd, noResolvedPluginError{}) + return cmd + } -func (c cli) newInitContext() plugin.Context { - return plugin.Context{ - CommandName: c.commandName, - Description: `Initialize a new project. + // Obtain the plugin keys and subcommands from the plugins that implement plugin.Init. + pluginKeys, subcommands := c.filterSubcommands( + func(p plugin.Plugin) bool { + _, isValid := p.(plugin.Init) + return isValid + }, + func(p plugin.Plugin) plugin.Subcommand { + return p.(plugin.Init).GetInitSubcommand() + }, + ) + + // Verify that there is at least one remaining plugin. + if len(subcommands) == 0 { + cmdErr(cmd, noAvailablePluginError{"project initialization"}) + return cmd + } -For further help about a specific project version, set --project-version. -`, - Examples: c.getInitHelpExamples(), + // Initialization methods. + _ = c.initializationMethods(cmd, subcommands) + + // Execution methods. + cfg := yamlstore.New(c.fs) + cmd.PreRunE = func(*cobra.Command, []string) error { + // Check if a config is initialized. + if err := cfg.Load(); err == nil || !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("%s: already initialized", initErrorMsg) + } + + err := cfg.New(c.projectVersion) + if err != nil { + return fmt.Errorf("%s: error initializing project configuration: %w", initErrorMsg, err) + } + + resolvedPluginKeys := make([]string, 0, len(c.resolvedPlugins)) + for _, p := range c.resolvedPlugins { + resolvedPluginKeys = append(resolvedPluginKeys, plugin.KeyFor(p)) + } + _ = cfg.Config().SetLayout(strings.Join(resolvedPluginKeys, ",")) + + // Inject config method. + for i, subcommand := range subcommands { + if subcmd, requiresConfig := subcommand.(plugin.RequiresConfig); requiresConfig { + if err := subcmd.InjectConfig(cfg.Config()); err != nil { + var exitError plugin.ExitError + if errors.As(err, &exitError) { + fmt.Printf("skipping %q: %s\n", pluginKeys[i], exitError.Reason) + subcommands = append(subcommands[:i], subcommands[i+1:]...) + } else { + return fmt.Errorf("%s: unable to inject the configuration to %q: %w", + initErrorMsg, pluginKeys[i], err) + } + } + } + } + + // Pre-scaffold method. + for i, subcommand := range subcommands { + if subcmd, hasPreScaffold := subcommand.(plugin.HasPreScaffold); hasPreScaffold { + if err := subcmd.PreScaffold(c.fs); err != nil { + var exitError plugin.ExitError + if errors.As(err, &exitError) { + fmt.Printf("skipping %q: %s\n", pluginKeys[i], exitError.Reason) + subcommands = append(subcommands[:i], subcommands[i+1:]...) + } else { + return fmt.Errorf("%s: unable to run pre-scaffold tasks of %q: %w", + initErrorMsg, pluginKeys[i], err) + } + } + } + } + + return nil } + cmd.RunE = executionMethodsRunEFunc(pluginKeys, subcommands, c.fs, initErrorMsg) + cmd.PostRunE = executionMethodsPostRunEFunc(pluginKeys, subcommands, cfg, initErrorMsg) + + return cmd } func (c cli) getInitHelpExamples() string { @@ -109,55 +183,3 @@ func (c cli) getAvailablePlugins() (pluginKeys []string) { sort.Strings(pluginKeys) return pluginKeys } - -func (c cli) bindInit(ctx plugin.Context, cmd *cobra.Command) { - if len(c.resolvedPlugins) == 0 { - cmdErr(cmd, fmt.Errorf("no resolved plugins, please specify plugins with --%s or/and --%s flags", - projectVersionFlag, pluginsFlag)) - return - } - - var initPlugin plugin.Init - for _, p := range c.resolvedPlugins { - tmpPlugin, isValid := p.(plugin.Init) - if isValid { - if initPlugin != nil { - err := fmt.Errorf("duplicate initialization plugins (%s, %s), use a more specific plugin key", - plugin.KeyFor(initPlugin), plugin.KeyFor(p)) - cmdErrNoHelp(cmd, err) - return - } - initPlugin = tmpPlugin - } - } - - if initPlugin == nil { - cmdErr(cmd, fmt.Errorf("resolved plugins do not provide a project init plugin: %v", c.pluginKeys)) - return - } - - cfg, err := internalconfig.New(c.projectVersion, internalconfig.DefaultPath) - if err != nil { - cmdErr(cmd, fmt.Errorf("unable to initialize the project configuration: %w", err)) - return - } - - subcommand := initPlugin.GetInitSubcommand() - subcommand.InjectConfig(cfg.Config) - subcommand.BindFlags(cmd.Flags()) - subcommand.UpdateContext(&ctx) - cmd.Long = ctx.Description - cmd.Example = ctx.Examples - cmd.RunE = func(*cobra.Command, []string) error { - // Check if a config is initialized in the command runner so the check - // doesn't erroneously fail other commands used in initialized projects. - _, err := internalconfig.Read() - if err == nil || os.IsExist(err) { - log.Fatal("config already initialized") - } - if err := subcommand.Run(); err != nil { - return fmt.Errorf("failed to initialize project with %q: %v", plugin.KeyFor(initPlugin), err) - } - return cfg.Save() - } -} diff --git a/pkg/cli/internal/config/config.go b/pkg/cli/internal/config/config.go deleted file mode 100644 index 0bcbd4ba014..00000000000 --- a/pkg/cli/internal/config/config.go +++ /dev/null @@ -1,193 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package config - -import ( - "errors" - "fmt" - "os" - - "github.com/spf13/afero" - "sigs.k8s.io/yaml" - - "sigs.k8s.io/kubebuilder/v3/pkg/config" -) - -const ( - // DefaultPath is the default path for the configuration file - DefaultPath = "PROJECT" -) - -func exists(fs afero.Fs, path string) (bool, error) { - // Look up the file - _, err := fs.Stat(path) - - // If we could find it the file exists - if err == nil || os.IsExist(err) { - return true, nil - } - - // Not existing and different errors are differentiated - if os.IsNotExist(err) { - err = nil - } - return false, err -} - -type versionedConfig struct { - Version config.Version -} - -func readFrom(fs afero.Fs, path string) (config.Config, error) { - // Read the file - in, err := afero.ReadFile(fs, path) //nolint:gosec - if err != nil { - return nil, err - } - - // Check the file version - var versioned versionedConfig - if err := yaml.Unmarshal(in, &versioned); err != nil { - return nil, err - } - - // Create the config object - var c config.Config - c, err = config.New(versioned.Version) - if err != nil { - return nil, err - } - - // Unmarshal the file content - if err := c.Unmarshal(in); err != nil { - return nil, err - } - - return c, nil -} - -// Read obtains the configuration from the default path but doesn't allow to persist changes -func Read() (config.Config, error) { - return ReadFrom(DefaultPath) -} - -// ReadFrom obtains the configuration from the provided path but doesn't allow to persist changes -func ReadFrom(path string) (config.Config, error) { - return readFrom(afero.NewOsFs(), path) -} - -// Config extends model/config.Config allowing to persist changes -// NOTE: the existence of Config structs in both model and internal packages is to guarantee that kubebuilder -// is the only project that can modify the file, while plugins can still receive the configuration -type Config struct { - config.Config - - // path stores where the config should be saved to - path string - // mustNotExist requires the file not to exist when saving it - mustNotExist bool - // fs is for testing. - fs afero.Fs -} - -// New creates a new configuration that will be stored at the provided path -func New(version config.Version, path string) (*Config, error) { - cfg, err := config.New(version) - if err != nil { - return nil, err - } - - return &Config{ - Config: cfg, - path: path, - mustNotExist: true, - fs: afero.NewOsFs(), - }, nil -} - -// Load obtains the configuration from the default path allowing to persist changes (Save method) -func Load() (*Config, error) { - return LoadFrom(DefaultPath) -} - -// LoadInitialized calls Load() but returns helpful error messages if the config -// does not exist. -func LoadInitialized() (*Config, error) { - c, err := Load() - if os.IsNotExist(err) { - return nil, errors.New("unable to find configuration file, project must be initialized") - } - return c, err -} - -// LoadFrom obtains the configuration from the provided path allowing to persist changes (Save method) -func LoadFrom(path string) (*Config, error) { - fs := afero.NewOsFs() - c, err := readFrom(fs, path) - return &Config{Config: c, path: path, fs: fs}, err -} - -// Save saves the configuration information -func (c Config) Save() error { - if c.fs == nil { - c.fs = afero.NewOsFs() - } - // If path is unset, it was created directly with `Config{}` - if c.path == "" { - return saveError{errors.New("no information where it should be stored, " + - "use one of the constructors (`New`, `Load` or `LoadFrom`) to create Config instances")} - } - - // If it is a new configuration, the path should not exist yet - if c.mustNotExist { - // Lets check that the file doesn't exist - alreadyExists, err := exists(c.fs, c.path) - if err != nil { - return saveError{err} - } - if alreadyExists { - return saveError{errors.New("configuration already exists in the provided path")} - } - } - - // Marshall into YAML - content, err := c.Marshal() - if err != nil { - return saveError{err} - } - - // Write the marshalled configuration - err = afero.WriteFile(c.fs, c.path, content, 0600) - if err != nil { - return saveError{fmt.Errorf("failed to save configuration to %s: %v", c.path, err)} - } - - return nil -} - -// Path returns the path for configuration file -func (c Config) Path() string { - return c.path -} - -type saveError struct { - err error -} - -func (e saveError) Error() string { - return fmt.Sprintf("unable to save the configuration: %v", e.err) -} diff --git a/pkg/cli/internal/config/config_test.go b/pkg/cli/internal/config/config_test.go deleted file mode 100644 index fe7d7ee4932..00000000000 --- a/pkg/cli/internal/config/config_test.go +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package config - -import ( - "os" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/spf13/afero" - - cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" -) - -var _ = Describe("Config", func() { - Context("Save", func() { - It("should success for valid configs", func() { - cfg := Config{ - Config: cfgv2.New(), - fs: afero.NewMemMapFs(), - path: DefaultPath, - } - Expect(cfg.Save()).To(Succeed()) - - cfgBytes, err := afero.ReadFile(cfg.fs, DefaultPath) - Expect(err).NotTo(HaveOccurred()) - Expect(string(cfgBytes)).To(Equal(`version: "2" -`)) - }) - - It("should fail if path is not provided", func() { - cfg := Config{ - Config: cfgv2.New(), - fs: afero.NewMemMapFs(), - } - Expect(cfg.Save()).NotTo(Succeed()) - }) - }) - - Context("readFrom", func() { - It("should success for valid configs", func() { - configStr := `domain: example.com -repo: github.com/example/project -version: "2"` - expectedConfig := cfgv2.New() - _ = expectedConfig.SetDomain("example.com") - _ = expectedConfig.SetRepository("github.com/example/project") - - fs := afero.NewMemMapFs() - Expect(afero.WriteFile(fs, DefaultPath, []byte(configStr), os.ModePerm)).To(Succeed()) - - cfg, err := readFrom(fs, DefaultPath) - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).To(Equal(expectedConfig)) - }) - }) -}) diff --git a/pkg/cli/resource.go b/pkg/cli/resource.go new file mode 100644 index 00000000000..a936e6c0e20 --- /dev/null +++ b/pkg/cli/resource.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cli + +import ( + "fmt" + "strings" + + "github.com/spf13/pflag" + + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +const ( + groupPresent = "group flag present but empty" + versionPresent = "version flag present but empty" + kindPresent = "kind flag present but empty" + groupRequired = "group cannot be empty if the domain is empty" + versionRequired = "version cannot be empty" + kindRequired = "kind cannot be empty" +) + +// ResourceOptions contains the information required to build a new resource.Resource. +type ResourceOptions struct { + resource.GVK +} + +func bindResourceFlags(fs *pflag.FlagSet) *ResourceOptions { + options := &ResourceOptions{} + + fs.StringVar(&options.Group, "group", "", "resource Group") + fs.StringVar(&options.Version, "version", "", "resource Version") + fs.StringVar(&options.Kind, "kind", "", "resource Kind") + + return options +} + +// Validate verifies that all the fields have valid values. +func (opts ResourceOptions) Validate() error { + // Check that the required flags did not get a flag as their value. + // We can safely look for a '-' as the first char as none of the fields accepts it. + // NOTE: We must do this for all the required flags first or we may output the wrong + // error as flags may seem to be missing because Cobra assigned them to another flag. + if strings.HasPrefix(opts.Group, "-") { + return fmt.Errorf(groupPresent) + } + if strings.HasPrefix(opts.Version, "-") { + return fmt.Errorf(versionPresent) + } + if strings.HasPrefix(opts.Kind, "-") { + return fmt.Errorf(kindPresent) + } + + // Now we can check that all the required flags are not empty. + if len(opts.Group) == 0 && len(opts.Domain) == 0 { + return fmt.Errorf(groupRequired) + } + if len(opts.Version) == 0 { + return fmt.Errorf(versionRequired) + } + if len(opts.Kind) == 0 { + return fmt.Errorf(kindRequired) + } + + return nil +} + +// NewResource creates a new resource from the options +func (opts ResourceOptions) NewResource() *resource.Resource { + return &resource.Resource{ + GVK: opts.GVK, + Plural: resource.RegularPlural(opts.Kind), + API: &resource.API{}, + Webhooks: &resource.Webhooks{}, + } +} diff --git a/pkg/cli/resource_test.go b/pkg/cli/resource_test.go new file mode 100644 index 00000000000..7b37449af72 --- /dev/null +++ b/pkg/cli/resource_test.go @@ -0,0 +1,113 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cli + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" + + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +var _ = Describe("ResourceOptions", func() { + const ( + group = "crew" + domain = "test.io" + version = "v1" + kind = "FirstMate" + ) + + var ( + fullGVK = resource.GVK{ + Group: group, + Domain: domain, + Version: version, + Kind: kind, + } + noDomainGVK = resource.GVK{ + Group: group, + Version: version, + Kind: kind, + } + noGroupGVK = resource.GVK{ + Domain: domain, + Version: version, + Kind: kind, + } + ) + + Context("Validate", func() { + DescribeTable("should succeed for valid options", + func(options ResourceOptions) { Expect(options.Validate()).To(Succeed()) }, + Entry("full GVK", ResourceOptions{GVK: fullGVK}), + Entry("missing domain", ResourceOptions{GVK: noDomainGVK}), + Entry("missing group", ResourceOptions{GVK: noGroupGVK}), + ) + + DescribeTable("should fail for invalid options", + func(options ResourceOptions) { Expect(options.Validate()).NotTo(Succeed()) }, + Entry("group flag captured another flag", ResourceOptions{GVK: resource.GVK{Group: "--version"}}), + Entry("version flag captured another flag", ResourceOptions{GVK: resource.GVK{Version: "--kind"}}), + Entry("kind flag captured another flag", ResourceOptions{GVK: resource.GVK{Kind: "--group"}}), + Entry("missing group and domain", ResourceOptions{GVK: resource.GVK{Version: version, Kind: kind}}), + Entry("missing version", ResourceOptions{GVK: resource.GVK{Group: group, Domain: domain, Kind: kind}}), + Entry("missing kind", ResourceOptions{GVK: resource.GVK{Group: group, Domain: domain, Version: version}}), + ) + }) + + Context("NewResource", func() { + DescribeTable("should succeed if the Resource is valid", + func(options ResourceOptions) { + Expect(options.Validate()).To(Succeed()) + + resource := options.NewResource() + Expect(resource.Validate()).To(Succeed()) + Expect(resource.GVK.IsEqualTo(options.GVK)).To(BeTrue()) + // Plural is checked in the next test + Expect(resource.Path).To(Equal("")) + Expect(resource.API).NotTo(BeNil()) + Expect(resource.API.CRDVersion).To(Equal("")) + Expect(resource.API.Namespaced).To(BeFalse()) + Expect(resource.Controller).To(BeFalse()) + Expect(resource.Webhooks).NotTo(BeNil()) + Expect(resource.Webhooks.WebhookVersion).To(Equal("")) + Expect(resource.Webhooks.Defaulting).To(BeFalse()) + Expect(resource.Webhooks.Validation).To(BeFalse()) + Expect(resource.Webhooks.Conversion).To(BeFalse()) + }, + Entry("full GVK", ResourceOptions{GVK: fullGVK}), + Entry("missing domain", ResourceOptions{GVK: noDomainGVK}), + Entry("missing group", ResourceOptions{GVK: noGroupGVK}), + ) + + DescribeTable("should default the Plural by pluralizing the Kind", + func(kind, plural string) { + options := ResourceOptions{GVK: resource.GVK{Group: group, Version: version, Kind: kind}} + Expect(options.Validate()).To(Succeed()) + + resource := options.NewResource() + Expect(resource.Validate()).To(Succeed()) + Expect(resource.GVK.IsEqualTo(options.GVK)).To(BeTrue()) + Expect(resource.Plural).To(Equal(plural)) + }, + Entry("for `FirstMate`", "FirstMate", "firstmates"), + Entry("for `Fish`", "Fish", "fish"), + Entry("for `Helmswoman`", "Helmswoman", "helmswomen"), + ) + }) +}) diff --git a/pkg/cli/cli_suite_test.go b/pkg/cli/suite_test.go similarity index 100% rename from pkg/cli/cli_suite_test.go rename to pkg/cli/suite_test.go diff --git a/pkg/cli/webhook.go b/pkg/cli/webhook.go index fb2e4511a66..e06430730d3 100644 --- a/pkg/cli/webhook.go +++ b/pkg/cli/webhook.go @@ -14,80 +14,58 @@ See the License for the specific language governing permissions and limitations under the License. */ -package cli // nolint:dupl +package cli //nolint:dupl import ( "fmt" "github.com/spf13/cobra" - "sigs.k8s.io/kubebuilder/v3/pkg/cli/internal/config" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) +const webhookErrorMsg = "failed to create webhook" + func (c cli) newCreateWebhookCmd() *cobra.Command { - ctx := c.newWebhookContext() cmd := &cobra.Command{ - Use: "webhook", - Short: "Scaffold a webhook for an API resource", - Long: ctx.Description, - Example: ctx.Examples, + Use: "webhook", + Short: "Scaffold a webhook for an API resource", + Long: `Scaffold a webhook for an API resource. +`, RunE: errCmdFunc( fmt.Errorf("webhook subcommand requires an existing project"), ), } - // Lookup the plugin for projectVersion and bind it to the command. - c.bindCreateWebhook(ctx, cmd) - return cmd -} - -func (c cli) newWebhookContext() plugin.Context { - return plugin.Context{ - CommandName: c.commandName, - Description: `Scaffold a webhook for an API resource. -`, - } -} - -// nolint:dupl -func (c cli) bindCreateWebhook(ctx plugin.Context, cmd *cobra.Command) { + // In case no plugin was resolved, instead of failing the construction of the CLI, fail the execution of + // this subcommand. This allows the use of subcommands that do not require resolved plugins like help. if len(c.resolvedPlugins) == 0 { - cmdErr(cmd, fmt.Errorf(noPluginError)) - return + cmdErr(cmd, noResolvedPluginError{}) + return cmd } - var createWebhookPlugin plugin.CreateWebhook - for _, p := range c.resolvedPlugins { - tmpPlugin, isValid := p.(plugin.CreateWebhook) - if isValid { - if createWebhookPlugin != nil { - err := fmt.Errorf("duplicate webhook creation plugins (%s, %s), use a more specific plugin key", - plugin.KeyFor(createWebhookPlugin), plugin.KeyFor(p)) - cmdErr(cmd, err) - return - } - createWebhookPlugin = tmpPlugin - } - } + // Obtain the plugin keys and subcommands from the plugins that implement plugin.CreateWebhook. + pluginKeys, subcommands := c.filterSubcommands( + func(p plugin.Plugin) bool { + _, isValid := p.(plugin.CreateWebhook) + return isValid + }, + func(p plugin.Plugin) plugin.Subcommand { + return p.(plugin.CreateWebhook).GetCreateWebhookSubcommand() + }, + ) - if createWebhookPlugin == nil { - cmdErr(cmd, fmt.Errorf("resolved plugins do not provide a webhook creation plugin: %v", c.pluginKeys)) - return + // Verify that there is at least one remaining plugin. + if len(subcommands) == 0 { + cmdErr(cmd, noAvailablePluginError{"webhook creation"}) + return cmd } - cfg, err := config.LoadInitialized() - if err != nil { - cmdErr(cmd, err) - return - } + // Initialization methods. + options := c.initializationMethods(cmd, subcommands) + + // Execution methods. + cmd.PreRunE, cmd.RunE, cmd.PostRunE = c.executionMethodsFuncs(pluginKeys, subcommands, options, webhookErrorMsg) - subcommand := createWebhookPlugin.GetCreateWebhookSubcommand() - subcommand.InjectConfig(cfg.Config) - subcommand.BindFlags(cmd.Flags()) - subcommand.UpdateContext(&ctx) - cmd.Long = ctx.Description - cmd.Example = ctx.Examples - cmd.RunE = runECmdFunc(cfg, subcommand, - fmt.Sprintf("failed to create webhook with %q", plugin.KeyFor(createWebhookPlugin))) + return cmd } diff --git a/pkg/config/store/errors.go b/pkg/config/store/errors.go new file mode 100644 index 00000000000..fd57aee0a9a --- /dev/null +++ b/pkg/config/store/errors.go @@ -0,0 +1,51 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package store + +import ( + "fmt" +) + +// LoadError wraps errors yielded by Store.Load and Store.LoadFrom methods +type LoadError struct { + Err error +} + +// Error implements error interface +func (e LoadError) Error() string { + return fmt.Sprintf("unable to load the configuration: %v", e.Err) +} + +// Unwrap implements Wrapper interface +func (e LoadError) Unwrap() error { + return e.Err +} + +// SaveError wraps errors yielded by Store.Save and Store.SaveTo methods +type SaveError struct { + Err error +} + +// Error implements error interface +func (e SaveError) Error() string { + return fmt.Sprintf("unable to save the configuration: %v", e.Err) +} + +// Unwrap implements Wrapper interface +func (e SaveError) Unwrap() error { + return e.Err +} diff --git a/pkg/config/store/errors_test.go b/pkg/config/store/errors_test.go new file mode 100644 index 00000000000..acc9b445b1c --- /dev/null +++ b/pkg/config/store/errors_test.go @@ -0,0 +1,68 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package store + +import ( + "fmt" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestConfigStore(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Config Store Suite") +} + +var _ = Describe("LoadError", func() { + var ( + wrapped = fmt.Errorf("error message") + err = LoadError{Err: wrapped} + ) + + Context("Error", func() { + It("should return the correct error message", func() { + Expect(err.Error()).To(Equal(fmt.Sprintf("unable to load the configuration: %v", wrapped))) + }) + }) + + Context("Unwrap", func() { + It("should unwrap to the wrapped error", func() { + Expect(err.Unwrap()).To(Equal(wrapped)) + }) + }) +}) + +var _ = Describe("SaveError", func() { + var ( + wrapped = fmt.Errorf("error message") + err = SaveError{Err: wrapped} + ) + + Context("Error", func() { + It("should return the correct error message", func() { + Expect(err.Error()).To(Equal(fmt.Sprintf("unable to save the configuration: %v", wrapped))) + }) + }) + + Context("Unwrap", func() { + It("should unwrap to the wrapped error", func() { + Expect(err.Unwrap()).To(Equal(wrapped)) + }) + }) +}) diff --git a/pkg/config/store/interface.go b/pkg/config/store/interface.go new file mode 100644 index 00000000000..2bb1a21cb01 --- /dev/null +++ b/pkg/config/store/interface.go @@ -0,0 +1,38 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package store + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/config" +) + +// Store represents a persistence backend for config.Config +type Store interface { + // New creates a new config.Config to store + New(config.Version) error + // Load retrieves the config.Config from the persistence backend + Load() error + // LoadFrom retrieves the config.Config from the persistence backend at the specified key + LoadFrom(string) error + // Save stores the config.Config into the persistence backend + Save() error + // SaveTo stores the config.Config into the persistence backend at the specified key + SaveTo(string) error + + // Config returns the stored config.Config + Config() config.Config +} diff --git a/pkg/config/store/yaml/store.go b/pkg/config/store/yaml/store.go new file mode 100644 index 00000000000..7ca80c2a018 --- /dev/null +++ b/pkg/config/store/yaml/store.go @@ -0,0 +1,147 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package yaml + +import ( + "fmt" + "os" + + "github.com/spf13/afero" + "sigs.k8s.io/yaml" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config/store" +) + +const ( + // DefaultPath is the default path for the configuration file + DefaultPath = "PROJECT" +) + +// yamlStore implements store.Store using a YAML file as the storage backend +// The key is translated into the YAML file path +type yamlStore struct { + // fs is the filesystem that will be used to store the config.Config + fs afero.Fs + // mustNotExist requires the file not to exist when saving it + mustNotExist bool + + cfg config.Config +} + +// New creates a new configuration that will be stored at the provided path +func New(fs afero.Fs) store.Store { + return &yamlStore{fs: fs} +} + +// New implements store.Store interface +func (s *yamlStore) New(version config.Version) error { + cfg, err := config.New(version) + if err != nil { + return err + } + + s.cfg = cfg + s.mustNotExist = true + return nil +} + +// Load implements store.Store interface +func (s *yamlStore) Load() error { + return s.LoadFrom(DefaultPath) +} + +type versionedConfig struct { + Version config.Version `json:"version"` +} + +// LoadFrom implements store.Store interface +func (s *yamlStore) LoadFrom(path string) error { + s.mustNotExist = false + + // Read the file + in, err := afero.ReadFile(s.fs, path) + if err != nil { + return store.LoadError{Err: fmt.Errorf("unable to read %q file: %w", path, err)} + } + + // Check the file version + var versioned versionedConfig + if err := yaml.Unmarshal(in, &versioned); err != nil { + return store.LoadError{Err: fmt.Errorf("unable to determine config version: %w", err)} + } + + // Create the config object + var cfg config.Config + cfg, err = config.New(versioned.Version) + if err != nil { + return store.LoadError{Err: fmt.Errorf("unable to create config for version %q: %w", versioned.Version, err)} + } + + // Unmarshal the file content + if err := cfg.Unmarshal(in); err != nil { + return store.LoadError{Err: fmt.Errorf("unable to unmarshal config at %q: %w", path, err)} + } + + s.cfg = cfg + return nil +} + +// Save implements store.Store interface +func (s yamlStore) Save() error { + return s.SaveTo(DefaultPath) +} + +// SaveTo implements store.Store interface +func (s yamlStore) SaveTo(path string) error { + // If yamlStore is unset, none of New, Load, or LoadFrom were called successfully + if s.cfg == nil { + return store.SaveError{Err: fmt.Errorf("undefined config, use one of the initializers: New, Load, LoadFrom")} + } + + // If it is a new configuration, the path should not exist yet + if s.mustNotExist { + // Lets check that the file doesn't exist + _, err := s.fs.Stat(path) + if os.IsNotExist(err) { + // This is exactly what we want + } else if err == nil || os.IsExist(err) { + return store.SaveError{Err: fmt.Errorf("configuration already exists in %q", path)} + } else { + return store.SaveError{Err: fmt.Errorf("unable to check for file prior existence: %w", err)} + } + } + + // Marshall into YAML + content, err := s.cfg.Marshal() + if err != nil { + return store.SaveError{Err: fmt.Errorf("unable to marshal to YAML: %w", err)} + } + + // Write the marshalled configuration + err = afero.WriteFile(s.fs, path, content, 0600) + if err != nil { + return store.SaveError{Err: fmt.Errorf("failed to save configuration to %q: %w", path, err)} + } + + return nil +} + +// Config implements store.Store interface +func (s yamlStore) Config() config.Config { + return s.cfg +} diff --git a/pkg/config/store/yaml/store_test.go b/pkg/config/store/yaml/store_test.go new file mode 100644 index 00000000000..5d3521f13f5 --- /dev/null +++ b/pkg/config/store/yaml/store_test.go @@ -0,0 +1,245 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package yaml + +import ( + "errors" + "os" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/spf13/afero" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config/store" + cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" +) + +func TestConfigStoreYaml(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Config Store YAML Suite") +} + +var _ = Describe("New", func() { + It("should return a new empty store", func() { + s := New(afero.NewMemMapFs()) + Expect(s.Config()).To(BeNil()) + + ys, ok := s.(*yamlStore) + Expect(ok).To(BeTrue()) + Expect(ys.fs).NotTo(BeNil()) + }) +}) + +var _ = Describe("yamlStore", func() { + const ( + v2File = `version: "2" +` + unversionedFile = `version: +` + nonexistentVersionFile = `version: 1-alpha +` // v1-alpha never existed + wrongFile = `version: "2" +layout: "" +` // layout field does not exist in v2 + ) + + var ( + s *yamlStore + + path = DefaultPath + "2" + ) + + BeforeEach(func() { + s = New(afero.NewMemMapFs()).(*yamlStore) + }) + + Context("New", func() { + It("should initialize a new Config backend for the provided version", func() { + Expect(s.New(cfgv2.Version)).To(Succeed()) + Expect(s.fs).NotTo(BeNil()) + Expect(s.mustNotExist).To(BeTrue()) + Expect(s.Config()).NotTo(BeNil()) + Expect(s.Config().GetVersion().Compare(cfgv2.Version)).To(Equal(0)) + }) + + It("should fail for an unregistered config version", func() { + Expect(s.New(config.Version{})).NotTo(Succeed()) + }) + }) + + Context("Load", func() { + It("should load the Config from an existing file at the default path", func() { + Expect(afero.WriteFile(s.fs, DefaultPath, []byte(v2File), os.ModePerm)).To(Succeed()) + + Expect(s.Load()).To(Succeed()) + Expect(s.fs).NotTo(BeNil()) + Expect(s.mustNotExist).To(BeFalse()) + Expect(s.Config()).NotTo(BeNil()) + Expect(s.Config().GetVersion().Compare(cfgv2.Version)).To(Equal(0)) + }) + + It("should fail if no file exists at the default path", func() { + err := s.Load() + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, &store.LoadError{})).To(BeTrue()) + }) + + It("should fail if unable to identify the version of the file at the default path", func() { + Expect(afero.WriteFile(s.fs, DefaultPath, []byte(unversionedFile), os.ModePerm)).To(Succeed()) + + err := s.Load() + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, &store.LoadError{})).To(BeTrue()) + }) + + It("should fail if unable to create a Config for the version of the file at the default path", func() { + Expect(afero.WriteFile(s.fs, DefaultPath, []byte(nonexistentVersionFile), os.ModePerm)).To(Succeed()) + + err := s.Load() + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, &store.LoadError{})).To(BeTrue()) + }) + + It("should fail if unable to unmarshal the file at the default path", func() { + Expect(afero.WriteFile(s.fs, DefaultPath, []byte(wrongFile), os.ModePerm)).To(Succeed()) + + err := s.Load() + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, &store.LoadError{})).To(BeTrue()) + }) + }) + + Context("LoadFrom", func() { + It("should load the Config from an existing file from the specified path", func() { + Expect(afero.WriteFile(s.fs, path, []byte(v2File), os.ModePerm)).To(Succeed()) + + Expect(s.LoadFrom(path)).To(Succeed()) + Expect(s.fs).NotTo(BeNil()) + Expect(s.mustNotExist).To(BeFalse()) + Expect(s.Config()).NotTo(BeNil()) + Expect(s.Config().GetVersion().Compare(cfgv2.Version)).To(Equal(0)) + }) + + It("should fail if no file exists at the specified path", func() { + err := s.LoadFrom(path) + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, &store.LoadError{})).To(BeTrue()) + }) + + It("should fail if unable to identify the version of the file at the specified path", func() { + Expect(afero.WriteFile(s.fs, path, []byte(unversionedFile), os.ModePerm)).To(Succeed()) + + err := s.LoadFrom(path) + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, &store.LoadError{})).To(BeTrue()) + }) + + It("should fail if unable to create a Config for the version of the file at the specified path", func() { + Expect(afero.WriteFile(s.fs, path, []byte(nonexistentVersionFile), os.ModePerm)).To(Succeed()) + + err := s.LoadFrom(path) + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, &store.LoadError{})).To(BeTrue()) + }) + + It("should fail if unable to unmarshal the file at the specified path", func() { + Expect(afero.WriteFile(s.fs, path, []byte(wrongFile), os.ModePerm)).To(Succeed()) + + err := s.LoadFrom(path) + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, &store.LoadError{})).To(BeTrue()) + }) + }) + + Context("Save", func() { + + It("should succeed for a valid config", func() { + s.cfg = cfgv2.New() + Expect(s.Save()).To(Succeed()) + + cfgBytes, err := afero.ReadFile(s.fs, DefaultPath) + Expect(err).NotTo(HaveOccurred()) + Expect(string(cfgBytes)).To(Equal(v2File)) + }) + + It("should succeed for a valid config that must not exist", func() { + s.cfg = cfgv2.New() + s.mustNotExist = true + Expect(s.Save()).To(Succeed()) + + cfgBytes, err := afero.ReadFile(s.fs, DefaultPath) + Expect(err).NotTo(HaveOccurred()) + Expect(string(cfgBytes)).To(Equal(v2File)) + }) + + It("should fail for an empty config", func() { + err := s.Save() + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, &store.SaveError{})).To(BeTrue()) + }) + + It("should fail for a pre-existent file that must not exist", func() { + s.cfg = cfgv2.New() + s.mustNotExist = true + Expect(afero.WriteFile(s.fs, DefaultPath, []byte(v2File), os.ModePerm)).To(Succeed()) + + err := s.Save() + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, &store.SaveError{})).To(BeTrue()) + }) + }) + + Context("SaveTo", func() { + It("should success for valid configs", func() { + s.cfg = cfgv2.New() + Expect(s.SaveTo(path)).To(Succeed()) + + cfgBytes, err := afero.ReadFile(s.fs, path) + Expect(err).NotTo(HaveOccurred()) + Expect(string(cfgBytes)).To(Equal(`version: "2" +`)) + }) + + It("should succeed for a valid config that must not exist", func() { + s.cfg = cfgv2.New() + s.mustNotExist = true + Expect(s.SaveTo(path)).To(Succeed()) + + cfgBytes, err := afero.ReadFile(s.fs, path) + Expect(err).NotTo(HaveOccurred()) + Expect(string(cfgBytes)).To(Equal(v2File)) + }) + + It("should fail for an empty config", func() { + err := s.SaveTo(path) + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, &store.SaveError{})).To(BeTrue()) + }) + + It("should fail for a pre-existent file that must not exist", func() { + s.cfg = cfgv2.New() + s.mustNotExist = true + Expect(afero.WriteFile(s.fs, path, []byte(v2File), os.ModePerm)).To(Succeed()) + + err := s.SaveTo(path) + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, &store.SaveError{})).To(BeTrue()) + }) + }) +}) diff --git a/pkg/config/v3/config.go b/pkg/config/v3/config.go index 144e9580d96..cbacc61521b 100644 --- a/pkg/config/v3/config.go +++ b/pkg/config/v3/config.go @@ -47,12 +47,11 @@ type cfg struct { Resources []resource.Resource `json:"resources,omitempty"` // Plugins - Plugins PluginConfigs `json:"plugins,omitempty"` + Plugins pluginConfigs `json:"plugins,omitempty"` } -// PluginConfigs holds a set of arbitrary plugin configuration objects mapped by plugin key. -// TODO: do not export this once internalconfig has merged with config -type PluginConfigs map[string]pluginConfig +// pluginConfigs holds a set of arbitrary plugin configuration objects mapped by plugin key. +type pluginConfigs map[string]pluginConfig // pluginConfig is an arbitrary plugin configuration object. type pluginConfig interface{} diff --git a/pkg/config/v3/config_test.go b/pkg/config/v3/config_test.go index cdd49628846..018451a0c9f 100644 --- a/pkg/config/v3/config_test.go +++ b/pkg/config/v3/config_test.go @@ -367,8 +367,8 @@ var _ = Describe("cfg", func() { Repository: repo, Name: name, Layout: layout, - Plugins: PluginConfigs{ - "plugin-x": map[string]interface{}{ + Plugins: pluginConfigs{ + key: map[string]interface{}{ "data-1": "", }, }, @@ -379,8 +379,8 @@ var _ = Describe("cfg", func() { Repository: repo, Name: name, Layout: layout, - Plugins: PluginConfigs{ - "plugin-x": map[string]interface{}{ + Plugins: pluginConfigs{ + key: map[string]interface{}{ "data-1": "plugin value 1", "data-2": "plugin value 2", }, @@ -399,6 +399,13 @@ var _ = Describe("cfg", func() { Expect(errors.As(err, &config.PluginKeyNotFoundError{})).To(BeTrue()) }) + It("DecodePluginConfig should fail to retrieve data from a non-existent plugin", func() { + var pluginConfig PluginConfig + err := c1.DecodePluginConfig("plugin-y", &pluginConfig) + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, &config.PluginKeyNotFoundError{})).To(BeTrue()) + }) + DescribeTable("DecodePluginConfig should retrieve the plugin data correctly", func(inputConfig cfg, expectedPluginConfig PluginConfig) { var pluginConfig PluginConfig @@ -488,7 +495,7 @@ var _ = Describe("cfg", func() { }, }, }, - Plugins: PluginConfigs{ + Plugins: pluginConfigs{ "plugin-x": map[string]interface{}{ "data-1": "single plugin datum", }, diff --git a/pkg/machinery/errors.go b/pkg/machinery/errors.go new file mode 100644 index 00000000000..f8aff194914 --- /dev/null +++ b/pkg/machinery/errors.go @@ -0,0 +1,75 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package machinery + +import ( + "fmt" +) + +// This file contains the errors returned by the scaffolding machinery +// They are exported to be able to check which kind of error was returned + +// ValidateError is a wrapper error that will be used for errors returned by RequiresValidation.Validate +type ValidateError struct { + error +} + +// Unwrap implements Wrapper interface +func (e ValidateError) Unwrap() error { + return e.error +} + +// SetTemplateDefaultsError is a wrapper error that will be used for errors returned by Template.SetTemplateDefaults +type SetTemplateDefaultsError struct { + error +} + +// Unwrap implements Wrapper interface +func (e SetTemplateDefaultsError) Unwrap() error { + return e.error +} + +// ModelAlreadyExistsError is returned if the file is expected not to exist but a previous model does +type ModelAlreadyExistsError struct { + path string +} + +// Error implements error interface +func (e ModelAlreadyExistsError) Error() string { + return fmt.Sprintf("failed to create %s: model already exists", e.path) +} + +// UnknownIfExistsActionError is returned if the if-exists-action is unknown +type UnknownIfExistsActionError struct { + path string + ifExistsAction IfExistsAction +} + +// Error implements error interface +func (e UnknownIfExistsActionError) Error() string { + return fmt.Sprintf("unknown behavior if file exists (%d) for %s", e.ifExistsAction, e.path) +} + +// FileAlreadyExistsError is returned if the file is expected not to exist but it does +type FileAlreadyExistsError struct { + path string +} + +// Error implements error interface +func (e FileAlreadyExistsError) Error() string { + return fmt.Sprintf("failed to create %s: file already exists", e.path) +} diff --git a/pkg/machinery/errors_test.go b/pkg/machinery/errors_test.go new file mode 100644 index 00000000000..b1513ca6a69 --- /dev/null +++ b/pkg/machinery/errors_test.go @@ -0,0 +1,48 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package machinery + +import ( + "errors" + "path/filepath" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +var _ = Describe("Errors", func() { + var ( + path = filepath.Join("path", "to", "file") + testErr = errors.New("test error") + ) + + DescribeTable("should contain the wrapped error", + func(err error) { + Expect(errors.Is(err, testErr)).To(BeTrue()) + }, + Entry("for validate errors", ValidateError{testErr}), + Entry("for set template defaults errors", SetTemplateDefaultsError{testErr}), + ) + + // NOTE: the following test increases coverage + It("should print a descriptive error message", func() { + Expect(ModelAlreadyExistsError{path}.Error()).To(ContainSubstring("model already exists")) + Expect(UnknownIfExistsActionError{path, -1}.Error()).To(ContainSubstring("unknown behavior if file exists")) + Expect(FileAlreadyExistsError{path}.Error()).To(ContainSubstring("file already exists")) + }) +}) diff --git a/pkg/model/file/file.go b/pkg/machinery/file.go similarity index 75% rename from pkg/model/file/file.go rename to pkg/machinery/file.go index 3b9db1cd341..bf053d14ce5 100644 --- a/pkg/model/file/file.go +++ b/pkg/machinery/file.go @@ -14,30 +14,30 @@ See the License for the specific language governing permissions and limitations under the License. */ -package file +package machinery // IfExistsAction determines what to do if the scaffold file already exists type IfExistsAction int const ( - // Skip skips the file and moves to the next one - Skip IfExistsAction = iota + // SkipFile skips the file and moves to the next one + SkipFile IfExistsAction = iota // Error returns an error and stops processing Error - // Overwrite truncates and overwrites the existing file - Overwrite + // OverwriteFile truncates and overwrites the existing file + OverwriteFile ) // File describes a file that will be written type File struct { // Path is the file to write - Path string `json:"path,omitempty"` + Path string // Contents is the generated output - Contents string `json:"contents,omitempty"` + Contents string // IfExistsAction determines what to do if the file exists - IfExistsAction IfExistsAction `json:"ifExistsAction,omitempty"` + IfExistsAction IfExistsAction } diff --git a/pkg/model/file/funcmap.go b/pkg/machinery/funcmap.go similarity index 86% rename from pkg/model/file/funcmap.go rename to pkg/machinery/funcmap.go index a0a9432427b..ac25e272d70 100644 --- a/pkg/model/file/funcmap.go +++ b/pkg/machinery/funcmap.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package file +package machinery import ( "fmt" @@ -39,10 +39,9 @@ func isEmptyString(s string) bool { } // hashFNV will generate a random string useful for generating a unique string -func hashFNV(s string) (string, error) { +func hashFNV(s string) string { hasher := fnv.New32a() - if _, err := hasher.Write([]byte(s)); err != nil { - return "", err - } - return fmt.Sprintf("%x", hasher.Sum(nil)), nil + // Hash.Write never returns an error + _, _ = hasher.Write([]byte(s)) + return fmt.Sprintf("%x", hasher.Sum(nil)) } diff --git a/pkg/machinery/funcmap_test.go b/pkg/machinery/funcmap_test.go new file mode 100644 index 00000000000..7bb33df48af --- /dev/null +++ b/pkg/machinery/funcmap_test.go @@ -0,0 +1,45 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package machinery + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +var _ = Describe("funcmap functions", func() { + Context("isEmptyString", func() { + It("should return true for empty strings", func() { + Expect(isEmptyString("")).To(BeTrue()) + }) + + DescribeTable("should return false for any other string", + func(str string) { Expect(isEmptyString(str)).To(BeFalse()) }, + Entry(`for "a"`, "a"), + Entry(`for "1"`, "1"), + Entry(`for "-"`, "-"), + Entry(`for "."`, "."), + ) + }) + + Context("hashFNV", func() { + It("should hash the input", func() { + Expect(hashFNV("test")).To(Equal("afd071e5")) + }) + }) +}) diff --git a/pkg/machinery/injector.go b/pkg/machinery/injector.go new file mode 100644 index 00000000000..5675a8a9ec3 --- /dev/null +++ b/pkg/machinery/injector.go @@ -0,0 +1,66 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package machinery + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +// injector is used to inject certain fields to file templates. +type injector struct { + // config stores the project configuration. + config config.Config + + // boilerplate is the copyright comment added at the top of scaffolded files. + boilerplate string + + // resource contains the information of the API that is being scaffolded. + resource *resource.Resource +} + +// injectInto injects fields from the universe into the builder +func (i injector) injectInto(builder Builder) { + // Inject project configuration + if i.config != nil { + if builderWithDomain, hasDomain := builder.(HasDomain); hasDomain { + builderWithDomain.InjectDomain(i.config.GetDomain()) + } + if builderWithRepository, hasRepository := builder.(HasRepository); hasRepository { + builderWithRepository.InjectRepository(i.config.GetRepository()) + } + if builderWithProjectName, hasProjectName := builder.(HasProjectName); hasProjectName { + builderWithProjectName.InjectProjectName(i.config.GetProjectName()) + } + if builderWithMultiGroup, hasMultiGroup := builder.(HasMultiGroup); hasMultiGroup { + builderWithMultiGroup.InjectMultiGroup(i.config.IsMultiGroup()) + } + if builderWithComponentConfig, hasComponentConfig := builder.(HasComponentConfig); hasComponentConfig { + builderWithComponentConfig.InjectComponentConfig(i.config.IsComponentConfig()) + } + } + // Inject boilerplate + if builderWithBoilerplate, hasBoilerplate := builder.(HasBoilerplate); hasBoilerplate { + builderWithBoilerplate.InjectBoilerplate(i.boilerplate) + } + // Inject resource + if i.resource != nil { + if builderWithResource, hasResource := builder.(HasResource); hasResource { + builderWithResource.InjectResource(i.resource) + } + } +} diff --git a/pkg/machinery/injector_test.go b/pkg/machinery/injector_test.go new file mode 100644 index 00000000000..7c5c3038f4b --- /dev/null +++ b/pkg/machinery/injector_test.go @@ -0,0 +1,295 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package machinery + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +type templateBase struct { + path string + ifExistsAction IfExistsAction +} + +func (t templateBase) GetPath() string { + return t.path +} + +func (t templateBase) GetIfExistsAction() IfExistsAction { + return t.ifExistsAction +} + +type templateWithDomain struct { + templateBase + domain string +} + +func (t *templateWithDomain) InjectDomain(domain string) { + t.domain = domain +} + +type templateWithRepository struct { + templateBase + repository string +} + +func (t *templateWithRepository) InjectRepository(repository string) { + t.repository = repository +} + +type templateWithProjectName struct { + templateBase + projectName string +} + +func (t *templateWithProjectName) InjectProjectName(projectName string) { + t.projectName = projectName +} + +type templateWithMultiGroup struct { + templateBase + multiGroup bool +} + +func (t *templateWithMultiGroup) InjectMultiGroup(multiGroup bool) { + t.multiGroup = multiGroup +} + +type templateWithComponentConfig struct { + templateBase + componentConfig bool +} + +func (t *templateWithComponentConfig) InjectComponentConfig(componentConfig bool) { + t.componentConfig = componentConfig +} + +type templateWithBoilerplate struct { + templateBase + boilerplate string +} + +func (t *templateWithBoilerplate) InjectBoilerplate(boilerplate string) { + t.boilerplate = boilerplate +} + +type templateWithResource struct { + templateBase + resource *resource.Resource +} + +func (t *templateWithResource) InjectResource(res *resource.Resource) { + t.resource = res +} + +var _ = Describe("injector", func() { + var tmp = templateBase{ + path: "my/path/to/file", + ifExistsAction: Error, + } + + Context("injectInto", func() { + Context("Config", func() { + var c config.Config + + BeforeEach(func() { + c = cfgv3.New() + }) + + Context("Domain", func() { + var template *templateWithDomain + + BeforeEach(func() { + template = &templateWithDomain{templateBase: tmp} + }) + + It("should not inject anything if the config is nil", func() { + injector{}.injectInto(template) + Expect(template.domain).To(Equal("")) + }) + + It("should not inject anything if the config doesn't have a domain set", func() { + injector{config: c}.injectInto(template) + Expect(template.domain).To(Equal("")) + }) + + It("should inject if the config has a domain set", func() { + const domain = "my.domain" + Expect(c.SetDomain(domain)).To(Succeed()) + + injector{config: c}.injectInto(template) + Expect(template.domain).To(Equal(domain)) + }) + }) + + Context("Repository", func() { + var template *templateWithRepository + + BeforeEach(func() { + template = &templateWithRepository{templateBase: tmp} + }) + + It("should not inject anything if the config is nil", func() { + injector{}.injectInto(template) + Expect(template.repository).To(Equal("")) + }) + + It("should not inject anything if the config doesn't have a repository set", func() { + injector{config: c}.injectInto(template) + Expect(template.repository).To(Equal("")) + }) + + It("should inject if the config has a repository set", func() { + const repo = "test" + Expect(c.SetRepository(repo)).To(Succeed()) + + injector{config: c}.injectInto(template) + Expect(template.repository).To(Equal(repo)) + }) + }) + + Context("Project name", func() { + var template *templateWithProjectName + + BeforeEach(func() { + template = &templateWithProjectName{templateBase: tmp} + }) + + It("should not inject anything if the config is nil", func() { + injector{}.injectInto(template) + Expect(template.projectName).To(Equal("")) + }) + + It("should not inject anything if the config doesn't have a project name set", func() { + injector{config: c}.injectInto(template) + Expect(template.projectName).To(Equal("")) + }) + + It("should inject if the config has a project name set", func() { + const projectName = "my project" + Expect(c.SetProjectName(projectName)).To(Succeed()) + + injector{config: c}.injectInto(template) + Expect(template.projectName).To(Equal(projectName)) + }) + }) + + Context("Multi-group", func() { + var template *templateWithMultiGroup + + BeforeEach(func() { + template = &templateWithMultiGroup{templateBase: tmp} + }) + + It("should not inject anything if the config is nil", func() { + injector{}.injectInto(template) + Expect(template.multiGroup).To(BeFalse()) + }) + + It("should not set the flag if the config doesn't have the multi-group flag set", func() { + injector{config: c}.injectInto(template) + Expect(template.multiGroup).To(BeFalse()) + }) + + It("should set the flag if the config has the multi-group flag set", func() { + Expect(c.SetMultiGroup()).To(Succeed()) + + injector{config: c}.injectInto(template) + Expect(template.multiGroup).To(BeTrue()) + }) + }) + + Context("Component config", func() { + var template *templateWithComponentConfig + + BeforeEach(func() { + template = &templateWithComponentConfig{templateBase: tmp} + }) + + It("should not inject anything if the config is nil", func() { + injector{}.injectInto(template) + Expect(template.componentConfig).To(BeFalse()) + }) + + It("should not set the flag if the config doesn't have the component config flag set", func() { + injector{config: c}.injectInto(template) + Expect(template.componentConfig).To(BeFalse()) + }) + + It("should set the flag if the config has the component config flag set", func() { + Expect(c.SetComponentConfig()).To(Succeed()) + + injector{config: c}.injectInto(template) + Expect(template.componentConfig).To(BeTrue()) + }) + }) + }) + + Context("Boilerplate", func() { + var template *templateWithBoilerplate + + BeforeEach(func() { + template = &templateWithBoilerplate{templateBase: tmp} + }) + + It("should not inject anything if no boilerplate was set", func() { + injector{}.injectInto(template) + Expect(template.boilerplate).To(Equal("")) + }) + + It("should inject if the a boilerplate was set", func() { + const boilerplate = `Copyright "The Kubernetes Authors"` + + injector{boilerplate: boilerplate}.injectInto(template) + Expect(template.boilerplate).To(Equal(boilerplate)) + }) + }) + + Context("Resource", func() { + var template *templateWithResource + + BeforeEach(func() { + template = &templateWithResource{templateBase: tmp} + }) + + It("should not inject anything if the resource is nil", func() { + injector{}.injectInto(template) + Expect(template.resource).To(BeNil()) + }) + + It("should inject if the config has a domain set", func() { + var res = &resource.Resource{ + GVK: resource.GVK{ + Group: "group", + Domain: "my.domain", + Version: "v1", + Kind: "Kind", + }, + } + + injector{resource: res}.injectInto(template) + Expect(template.resource).To(Equal(res)) + }) + + }) + }) +}) diff --git a/pkg/model/file/interfaces.go b/pkg/machinery/interfaces.go similarity index 99% rename from pkg/model/file/interfaces.go rename to pkg/machinery/interfaces.go index 43470df4e60..73837dd8b30 100644 --- a/pkg/model/file/interfaces.go +++ b/pkg/machinery/interfaces.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package file +package machinery import ( "text/template" @@ -67,6 +67,12 @@ type HasRepository interface { InjectRepository(string) } +// HasProjectName allows a project name to be used on a template. +type HasProjectName interface { + // InjectProjectName sets the template project name. + InjectProjectName(string) +} + // HasMultiGroup allows the multi-group flag to be used on a template type HasMultiGroup interface { // InjectMultiGroup sets the template multi-group flag @@ -91,12 +97,6 @@ type HasResource interface { InjectResource(*resource.Resource) } -// HasProjectName allows a project name to be used on a template. -type HasProjectName interface { - // InjectProjectName sets the template project name. - InjectProjectName(string) -} - // UseCustomFuncMap allows a template to use a custom template.FuncMap instead of the default FuncMap. type UseCustomFuncMap interface { // GetFuncMap returns a custom FuncMap. diff --git a/pkg/plugins/internal/filesystem/errors.go b/pkg/machinery/internal/filesystem/errors.go similarity index 100% rename from pkg/plugins/internal/filesystem/errors.go rename to pkg/machinery/internal/filesystem/errors.go diff --git a/pkg/plugins/internal/filesystem/errors_test.go b/pkg/machinery/internal/filesystem/errors_test.go similarity index 100% rename from pkg/plugins/internal/filesystem/errors_test.go rename to pkg/machinery/internal/filesystem/errors_test.go diff --git a/pkg/plugins/internal/filesystem/filesystem.go b/pkg/machinery/internal/filesystem/filesystem.go similarity index 97% rename from pkg/plugins/internal/filesystem/filesystem.go rename to pkg/machinery/internal/filesystem/filesystem.go index e7e362c5c44..26cf4ba31ef 100644 --- a/pkg/plugins/internal/filesystem/filesystem.go +++ b/pkg/machinery/internal/filesystem/filesystem.go @@ -53,10 +53,10 @@ type fileSystem struct { } // New returns a new FileSystem -func New(options ...Options) FileSystem { +func New(underlying afero.Fs, options ...Options) FileSystem { // Default values fs := fileSystem{ - fs: afero.NewOsFs(), + fs: underlying, dirPerm: defaultDirectoryPermission, filePerm: defaultFilePermission, fileMode: createOrUpdate, diff --git a/pkg/plugins/internal/filesystem/filesystem_test.go b/pkg/machinery/internal/filesystem/filesystem_test.go similarity index 92% rename from pkg/plugins/internal/filesystem/filesystem_test.go rename to pkg/machinery/internal/filesystem/filesystem_test.go index 9f98ce400ac..cee72d8a9ea 100644 --- a/pkg/plugins/internal/filesystem/filesystem_test.go +++ b/pkg/machinery/internal/filesystem/filesystem_test.go @@ -22,6 +22,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/spf13/afero" ) func TestFileSystem(t *testing.T) { @@ -44,7 +45,7 @@ var _ = Describe("FileSystem", func() { Context("when using no options", func() { BeforeEach(func() { - fsi = New() + fsi = New(afero.NewMemMapFs()) fs, ok = fsi.(fileSystem) }) @@ -71,7 +72,7 @@ var _ = Describe("FileSystem", func() { Context("when using directory permission option", func() { BeforeEach(func() { - fsi = New(DirectoryPermissions(dirPerm)) + fsi = New(afero.NewMemMapFs(), DirectoryPermissions(dirPerm)) fs, ok = fsi.(fileSystem) }) @@ -98,7 +99,7 @@ var _ = Describe("FileSystem", func() { Context("when using file permission option", func() { BeforeEach(func() { - fsi = New(FilePermissions(filePerm)) + fsi = New(afero.NewMemMapFs(), FilePermissions(filePerm)) fs, ok = fsi.(fileSystem) }) @@ -125,7 +126,7 @@ var _ = Describe("FileSystem", func() { Context("when using both directory and file permission options", func() { BeforeEach(func() { - fsi = New(DirectoryPermissions(dirPerm), FilePermissions(filePerm)) + fsi = New(afero.NewMemMapFs(), DirectoryPermissions(dirPerm), FilePermissions(filePerm)) fs, ok = fsi.(fileSystem) }) diff --git a/pkg/plugins/internal/filesystem/mock.go b/pkg/machinery/internal/filesystem/mock.go similarity index 100% rename from pkg/plugins/internal/filesystem/mock.go rename to pkg/machinery/internal/filesystem/mock.go diff --git a/pkg/plugins/internal/filesystem/mock_test.go b/pkg/machinery/internal/filesystem/mock_test.go similarity index 100% rename from pkg/plugins/internal/filesystem/mock_test.go rename to pkg/machinery/internal/filesystem/mock_test.go diff --git a/pkg/model/file/marker.go b/pkg/machinery/marker.go similarity index 87% rename from pkg/model/file/marker.go rename to pkg/machinery/marker.go index a5b22c65502..e048615c6fa 100644 --- a/pkg/model/file/marker.go +++ b/pkg/machinery/marker.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package file +package machinery import ( "fmt" @@ -45,7 +45,11 @@ func NewMarkerFor(path string, value string) Marker { return Marker{comment, value} } - panic(fmt.Errorf("unknown file extension: '%s', expected '.go', '.yaml' or '.yml'", ext)) + extensions := make([]string, 0, len(commentsByExt)) + for extension := range commentsByExt { + extensions = append(extensions, fmt.Sprintf("%q", extension)) + } + panic(fmt.Errorf("unknown file extension: '%s', expected one of: %s", ext, strings.Join(extensions, ", "))) } // String implements Stringer diff --git a/pkg/machinery/marker_test.go b/pkg/machinery/marker_test.go new file mode 100644 index 00000000000..2f4468ff042 --- /dev/null +++ b/pkg/machinery/marker_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package machinery + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +var _ = Describe("NerMarkerFor", func() { + DescribeTable("should create valid markers for known extensions", + func(path, comment string) { Expect(NewMarkerFor(path, "").comment).To(Equal(comment)) }, + Entry("for go files", "file.go", "//"), + Entry("for yaml files", "file.yaml", "#"), + Entry("for yaml files (short version)", "file.yml", "#"), + ) + + It("should panic for unknown extensions", func() { + // testing panics require to use a function with no arguments + Expect(func() { NewMarkerFor("file.unkownext", "") }).To(Panic()) + }) +}) + +var _ = Describe("Marker", func() { + Context("String", func() { + DescribeTable("should return the right string representation", + func(marker Marker, str string) { Expect(marker.String()).To(Equal(str)) }, + Entry("for go files", Marker{comment: "//", value: "test"}, "//+kubebuilder:scaffold:test"), + Entry("for yaml files", Marker{comment: "#", value: "test"}, "#+kubebuilder:scaffold:test"), + ) + }) +}) diff --git a/pkg/model/file/mixins.go b/pkg/machinery/mixins.go similarity index 99% rename from pkg/model/file/mixins.go rename to pkg/machinery/mixins.go index fcf1dbbbf58..9529e687bf6 100644 --- a/pkg/model/file/mixins.go +++ b/pkg/machinery/mixins.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package file +package machinery import ( "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" @@ -64,7 +64,7 @@ type InserterMixin struct { // GetIfExistsAction implements Builder func (t *InserterMixin) GetIfExistsAction() IfExistsAction { // Inserter builders always need to overwrite previous files - return Overwrite + return OverwriteFile } // DomainMixin provides templates with a injectable domain field @@ -93,6 +93,18 @@ func (m *RepositoryMixin) InjectRepository(repository string) { } } +// ProjectNameMixin provides templates with an injectable project name field. +type ProjectNameMixin struct { + ProjectName string +} + +// InjectProjectName implements HasProjectName. +func (m *ProjectNameMixin) InjectProjectName(projectName string) { + if m.ProjectName == "" { + m.ProjectName = projectName + } +} + // MultiGroupMixin provides templates with a injectable multi-group flag field type MultiGroupMixin struct { // MultiGroup is the multi-group flag @@ -139,15 +151,3 @@ func (m *ResourceMixin) InjectResource(res *resource.Resource) { m.Resource = res } } - -// ProjectNameMixin provides templates with an injectable project name field. -type ProjectNameMixin struct { - ProjectName string -} - -// InjectProjectName implements HasProjectName. -func (m *ProjectNameMixin) InjectProjectName(projectName string) { - if m.ProjectName == "" { - m.ProjectName = projectName - } -} diff --git a/pkg/machinery/mixins_test.go b/pkg/machinery/mixins_test.go new file mode 100644 index 00000000000..944e4c0c195 --- /dev/null +++ b/pkg/machinery/mixins_test.go @@ -0,0 +1,188 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package machinery + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +type mockTemplate struct { + TemplateMixin + DomainMixin + RepositoryMixin + ProjectNameMixin + MultiGroupMixin + ComponentConfigMixin + BoilerplateMixin + ResourceMixin +} + +type mockInserter struct { + // InserterMixin requires a different type because it collides with TemplateMixin + InserterMixin +} + +var _ = Describe("TemplateMixin", func() { + const ( + path = "path/to/file.go" + ifExistsAction = SkipFile + body = "content" + ) + + var tmp = mockTemplate{ + TemplateMixin: TemplateMixin{ + PathMixin: PathMixin{path}, + IfExistsActionMixin: IfExistsActionMixin{ifExistsAction}, + TemplateBody: body, + }, + } + + Context("GetPath", func() { + It("should return the path", func() { + Expect(tmp.GetPath()).To(Equal(path)) + }) + }) + + Context("GetIfExistsAction", func() { + It("should return the if-exists action", func() { + Expect(tmp.GetIfExistsAction()).To(Equal(ifExistsAction)) + }) + }) + + Context("GetBody", func() { + It("should return the body", func() { + Expect(tmp.GetBody()).To(Equal(body)) + }) + }) +}) + +var _ = Describe("InserterMixin", func() { + const path = "path/to/file.go" + + var tmp = mockInserter{ + InserterMixin: InserterMixin{ + PathMixin: PathMixin{path}, + }, + } + + Context("GetPath", func() { + It("should return the path", func() { + Expect(tmp.GetPath()).To(Equal(path)) + }) + }) + + Context("GetIfExistsAction", func() { + It("should return overwrite file always", func() { + Expect(tmp.GetIfExistsAction()).To(Equal(OverwriteFile)) + }) + }) +}) + +var _ = Describe("DomainMixin", func() { + const domain = "my.domain" + + var tmp = mockTemplate{} + + Context("InjectDomain", func() { + It("should inject the provided domain", func() { + tmp.InjectDomain(domain) + Expect(tmp.Domain).To(Equal(domain)) + }) + }) +}) + +var _ = Describe("RepositoryMixin", func() { + const repo = "test" + + var tmp = mockTemplate{} + + Context("InjectRepository", func() { + It("should inject the provided repository", func() { + tmp.InjectRepository(repo) + Expect(tmp.Repo).To(Equal(repo)) + }) + }) +}) + +var _ = Describe("ProjectNameMixin", func() { + const name = "my project" + + var tmp = mockTemplate{} + + Context("InjectProjectName", func() { + It("should inject the provided project name", func() { + tmp.InjectProjectName(name) + Expect(tmp.ProjectName).To(Equal(name)) + }) + }) +}) + +var _ = Describe("MultiGroupMixin", func() { + var tmp = mockTemplate{} + + Context("InjectMultiGroup", func() { + It("should inject the provided multi group flag", func() { + tmp.InjectMultiGroup(true) + Expect(tmp.MultiGroup).To(BeTrue()) + }) + }) +}) + +var _ = Describe("ComponentConfigMixin", func() { + var tmp = mockTemplate{} + + Context("InjectComponentConfig", func() { + It("should inject the provided component config flag", func() { + tmp.InjectComponentConfig(true) + Expect(tmp.ComponentConfig).To(BeTrue()) + }) + }) +}) + +var _ = Describe("BoilerplateMixin", func() { + const boilerplate = "Copyright" + + var tmp = mockTemplate{} + + Context("InjectBoilerplate", func() { + It("should inject the provided boilerplate", func() { + tmp.InjectBoilerplate(boilerplate) + Expect(tmp.Boilerplate).To(Equal(boilerplate)) + }) + }) +}) + +var _ = Describe("ResourceMixin", func() { + var res = &resource.Resource{GVK: resource.GVK{ + Group: "group", + Domain: "my.domain", + Version: "v1", + Kind: "Kind", + }} + + var tmp = mockTemplate{} + + Context("InjectResource", func() { + It("should inject the provided resource", func() { + tmp.InjectResource(res) + Expect(tmp.Resource.GVK.IsEqualTo(res.GVK)).To(BeTrue()) + }) + }) +}) diff --git a/pkg/plugins/internal/machinery/scaffold.go b/pkg/machinery/scaffold.go similarity index 68% rename from pkg/plugins/internal/machinery/scaffold.go rename to pkg/machinery/scaffold.go index 5391c45bb2e..bfcfd1a3118 100644 --- a/pkg/plugins/internal/machinery/scaffold.go +++ b/pkg/machinery/scaffold.go @@ -25,11 +25,12 @@ import ( "strings" "text/template" + "github.com/spf13/afero" "golang.org/x/tools/imports" - "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/filesystem" + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery/internal/filesystem" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) var options = imports.Options{ @@ -42,71 +43,90 @@ var options = imports.Options{ // Scaffold uses templates to scaffold new files type Scaffold interface { // Execute writes to disk the provided files - Execute(*model.Universe, ...file.Builder) error + Execute(...Builder) error } // scaffold implements Scaffold interface type scaffold struct { - // plugins is the list of plugins we should allow to transform our generated scaffolding - plugins []model.Plugin - // fs allows to mock the file system for tests fs filesystem.FileSystem + + // injector is used to provide several fields to the templates + injector injector } +// ScaffoldOption allows to provide optional arguments to the Scaffold +type ScaffoldOption func(*scaffold) + // NewScaffold returns a new Scaffold with the provided plugins -func NewScaffold(plugins ...model.Plugin) Scaffold { - return &scaffold{ - plugins: plugins, - fs: filesystem.New(), +func NewScaffold(fs afero.Fs, options ...ScaffoldOption) Scaffold { + s := &scaffold{fs: filesystem.New(fs)} + + for _, option := range options { + option(s) } + + return s } -// Execute implements Scaffold.Execute -func (s *scaffold) Execute(universe *model.Universe, files ...file.Builder) error { - // Initialize the universe files - universe.Files = make(map[string]*file.File, len(files)) +// WithConfig provides the project configuration to the Scaffold +func WithConfig(cfg config.Config) ScaffoldOption { + return func(s *scaffold) { + s.injector.config = cfg - // Set the repo as the local prefix so that it knows how to group imports - if universe.Config != nil { - imports.LocalPrefix = universe.Config.GetRepository() + if cfg != nil && cfg.GetRepository() != "" { + imports.LocalPrefix = cfg.GetRepository() + } } +} - for _, f := range files { +// WithBoilerplate provides the boilerplate to the Scaffold +func WithBoilerplate(boilerplate string) ScaffoldOption { + return func(s *scaffold) { + s.injector.boilerplate = boilerplate + } +} + +// WithResource provides the resource to the Scaffold +func WithResource(resource *resource.Resource) ScaffoldOption { + return func(s *scaffold) { + s.injector.resource = resource + } +} + +// Execute implements Scaffold.Execute +func (s *scaffold) Execute(builders ...Builder) error { + // Initialize the files + files := make(map[string]*File, len(builders)) + + for _, builder := range builders { // Inject common fields - universe.InjectInto(f) + s.injector.injectInto(builder) // Validate file builders - if reqValFile, requiresValidation := f.(file.RequiresValidation); requiresValidation { - if err := reqValFile.Validate(); err != nil { - return file.NewValidateError(err) + if reqValBuilder, requiresValidation := builder.(RequiresValidation); requiresValidation { + if err := reqValBuilder.Validate(); err != nil { + return ValidateError{err} } } // Build models for Template builders - if t, isTemplate := f.(file.Template); isTemplate { - if err := s.buildFileModel(t, universe.Files); err != nil { + if t, isTemplate := builder.(Template); isTemplate { + if err := s.buildFileModel(t, files); err != nil { return err } } // Build models for Inserter builders - if i, isInserter := f.(file.Inserter); isInserter { - if err := s.updateFileModel(i, universe.Files); err != nil { + if i, isInserter := builder.(Inserter); isInserter { + if err := s.updateFileModel(i, files); err != nil { return err } } } - // Execute plugins - for _, plugin := range s.plugins { - if err := plugin.Pipe(universe); err != nil { - return model.NewPluginError(err) - } - } - // Persist the files to disk - for _, f := range universe.Files { + for _, f := range files { if err := s.writeFile(f); err != nil { return err } @@ -116,27 +136,27 @@ func (s *scaffold) Execute(universe *model.Universe, files ...file.Builder) erro } // buildFileModel scaffolds a single file -func (scaffold) buildFileModel(t file.Template, models map[string]*file.File) error { +func (scaffold) buildFileModel(t Template, models map[string]*File) error { // Set the template default values err := t.SetTemplateDefaults() if err != nil { - return file.NewSetTemplateDefaultsError(err) + return SetTemplateDefaultsError{err} } // Handle already existing models if _, found := models[t.GetPath()]; found { switch t.GetIfExistsAction() { - case file.Skip: + case SkipFile: return nil - case file.Error: - return modelAlreadyExistsError{t.GetPath()} - case file.Overwrite: + case Error: + return ModelAlreadyExistsError{t.GetPath()} + case OverwriteFile: default: - return unknownIfExistsActionError{t.GetPath(), t.GetIfExistsAction()} + return UnknownIfExistsActionError{t.GetPath(), t.GetIfExistsAction()} } } - m := &file.File{ + m := &File{ Path: t.GetPath(), IfExistsAction: t.GetIfExistsAction(), } @@ -152,7 +172,7 @@ func (scaffold) buildFileModel(t file.Template, models map[string]*file.File) er } // doTemplate executes the template for a file using the input -func doTemplate(t file.Template) ([]byte, error) { +func doTemplate(t Template) ([]byte, error) { temp, err := newTemplate(t).Parse(t.GetBody()) if err != nil { return nil, err @@ -178,9 +198,9 @@ func doTemplate(t file.Template) ([]byte, error) { } // newTemplate a new template with common functions -func newTemplate(t file.Template) *template.Template { - fm := file.DefaultFuncMap() - useFM, ok := t.(file.UseCustomFuncMap) +func newTemplate(t Template) *template.Template { + fm := DefaultFuncMap() + useFM, ok := t.(UseCustomFuncMap) if ok { fm = useFM.GetFuncMap() } @@ -188,7 +208,7 @@ func newTemplate(t file.Template) *template.Template { } // updateFileModel updates a single file -func (s scaffold) updateFileModel(i file.Inserter, models map[string]*file.File) error { +func (s scaffold) updateFileModel(i Inserter, models map[string]*File) error { m, err := s.loadPreviousModel(i, models) if err != nil { return err @@ -223,13 +243,13 @@ func (s scaffold) updateFileModel(i file.Inserter, models map[string]*file.File) } m.Contents = string(formattedContent) - m.IfExistsAction = file.Overwrite + m.IfExistsAction = OverwriteFile models[m.Path] = m return nil } // loadPreviousModel gets the previous model from the models map or the actual file -func (s scaffold) loadPreviousModel(i file.Inserter, models map[string]*file.File) (*file.File, error) { +func (s scaffold) loadPreviousModel(i Inserter, models map[string]*File) (*File, error) { // Lets see if we already have a model for this file if m, found := models[i.GetPath()]; found { // Check if there is already an scaffolded file @@ -245,21 +265,21 @@ func (s scaffold) loadPreviousModel(i file.Inserter, models map[string]*file.Fil // If both a model and a file are found, check which has preference switch m.IfExistsAction { - case file.Skip: + case SkipFile: // File has preference fromFile, err := s.loadModelFromFile(i.GetPath()) if err != nil { return m, nil } return fromFile, nil - case file.Error: + case Error: // Writing will result in an error, so we can return error now - return nil, fileAlreadyExistsError{i.GetPath()} - case file.Overwrite: + return nil, FileAlreadyExistsError{i.GetPath()} + case OverwriteFile: // Model has preference return m, nil default: - return nil, unknownIfExistsActionError{i.GetPath(), m.IfExistsAction} + return nil, UnknownIfExistsActionError{i.GetPath(), m.IfExistsAction} } } @@ -268,7 +288,7 @@ func (s scaffold) loadPreviousModel(i file.Inserter, models map[string]*file.Fil } // loadModelFromFile gets the previous model from the actual file -func (s scaffold) loadModelFromFile(path string) (f *file.File, err error) { +func (s scaffold) loadModelFromFile(path string) (f *File, err error) { reader, err := s.fs.Open(path) if err != nil { return @@ -285,12 +305,12 @@ func (s scaffold) loadModelFromFile(path string) (f *file.File, err error) { return } - f = &file.File{Path: path, Contents: string(content)} + f = &File{Path: path, Contents: string(content)} return } // getValidCodeFragments obtains the code fragments from a file.Inserter -func getValidCodeFragments(i file.Inserter) file.CodeFragmentsMap { +func getValidCodeFragments(i Inserter) CodeFragmentsMap { // Get the code fragments codeFragments := i.GetCodeFragments() @@ -314,7 +334,7 @@ func getValidCodeFragments(i file.Inserter) file.CodeFragmentsMap { // filterExistingValues removes the single-line values that already exists // TODO: Add support for multi-line duplicate values -func filterExistingValues(content string, codeFragmentsMap file.CodeFragmentsMap) error { +func filterExistingValues(content string, codeFragmentsMap CodeFragmentsMap) error { scanner := bufio.NewScanner(strings.NewReader(content)) for scanner.Scan() { line := scanner.Text() @@ -335,7 +355,7 @@ func filterExistingValues(content string, codeFragmentsMap file.CodeFragmentsMap return nil } -func insertStrings(content string, codeFragmentsMap file.CodeFragmentsMap) ([]byte, error) { +func insertStrings(content string, codeFragmentsMap CodeFragmentsMap) ([]byte, error) { out := new(bytes.Buffer) scanner := bufio.NewScanner(strings.NewReader(content)) @@ -359,7 +379,7 @@ func insertStrings(content string, codeFragmentsMap file.CodeFragmentsMap) ([]by return out.Bytes(), nil } -func (s scaffold) writeFile(f *file.File) error { +func (s scaffold) writeFile(f *File) error { // Check if the file to write already exists exists, err := s.fs.Exists(f.Path) if err != nil { @@ -367,14 +387,14 @@ func (s scaffold) writeFile(f *file.File) error { } if exists { switch f.IfExistsAction { - case file.Overwrite: + case OverwriteFile: // By not returning, the file is written as if it didn't exist - case file.Skip: + case SkipFile: // By returning nil, the file is not written but the process will carry on return nil - case file.Error: + case Error: // By returning an error, the file is not written and the process will fail - return fileAlreadyExistsError{f.Path} + return FileAlreadyExistsError{f.Path} } } diff --git a/pkg/plugins/internal/machinery/scaffold_test.go b/pkg/machinery/scaffold_test.go similarity index 64% rename from pkg/plugins/internal/machinery/scaffold_test.go rename to pkg/machinery/scaffold_test.go index d7e2a6c35e8..8ea8214cb43 100644 --- a/pkg/plugins/internal/machinery/scaffold_test.go +++ b/pkg/machinery/scaffold_test.go @@ -17,85 +17,66 @@ package machinery import ( "bytes" "errors" - "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" + "github.com/spf13/afero" - "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/filesystem" + cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery/internal/filesystem" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) -func TestScaffold(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Scaffold suite") -} - var _ = Describe("Scaffold", func() { Describe("NewScaffold", func() { - var ( - si Scaffold - s *scaffold - ok bool - ) - - Context("when using no plugins", func() { - BeforeEach(func() { - si = NewScaffold() - s, ok = si.(*scaffold) - }) - - It("should be a scaffold instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should not have a nil fs", func() { - Expect(s.fs).NotTo(BeNil()) - }) - - It("should not have any plugin", func() { - Expect(len(s.plugins)).To(Equal(0)) - }) + It("should succeed for no option", func() { + s, ok := NewScaffold(afero.NewMemMapFs()).(*scaffold) + Expect(ok).To(BeTrue()) + Expect(s.fs).NotTo(BeNil()) + Expect(s.injector.config).To(BeNil()) + Expect(s.injector.boilerplate).To(Equal("")) + Expect(s.injector.resource).To(BeNil()) }) - Context("when using one plugin", func() { - BeforeEach(func() { - si = NewScaffold(fakePlugin{}) - s, ok = si.(*scaffold) - }) - - It("should be a scaffold instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should not have a nil fs", func() { - Expect(s.fs).NotTo(BeNil()) - }) + It("should succeed with config option", func() { + cfg := cfgv3.New() - It("should have one plugin", func() { - Expect(len(s.plugins)).To(Equal(1)) - }) + s, ok := NewScaffold(afero.NewMemMapFs(), WithConfig(cfg)).(*scaffold) + Expect(ok).To(BeTrue()) + Expect(s.fs).NotTo(BeNil()) + Expect(s.injector.config).NotTo(BeNil()) + Expect(s.injector.config.GetVersion().Compare(cfgv3.Version)).To(Equal(0)) + Expect(s.injector.boilerplate).To(Equal("")) + Expect(s.injector.resource).To(BeNil()) }) - Context("when using several plugins", func() { - BeforeEach(func() { - si = NewScaffold(fakePlugin{}, fakePlugin{}, fakePlugin{}) - s, ok = si.(*scaffold) - }) - - It("should be a scaffold instance", func() { - Expect(ok).To(BeTrue()) - }) + It("should succeed with boilerplate option", func() { + const boilerplate = "Copyright" - It("should not have a nil fs", func() { - Expect(s.fs).NotTo(BeNil()) - }) + s, ok := NewScaffold(afero.NewMemMapFs(), WithBoilerplate(boilerplate)).(*scaffold) + Expect(ok).To(BeTrue()) + Expect(s.fs).NotTo(BeNil()) + Expect(s.injector.config).To(BeNil()) + Expect(s.injector.boilerplate).To(Equal(boilerplate)) + Expect(s.injector.resource).To(BeNil()) + }) - It("should have several plugins", func() { - Expect(len(s.plugins)).To(Equal(3)) - }) + It("should succeed with resource option", func() { + var res = &resource.Resource{GVK: resource.GVK{ + Group: "group", + Domain: "my.domain", + Version: "v1", + Kind: "Kind", + }} + + s, ok := NewScaffold(afero.NewMemMapFs(), WithResource(res)).(*scaffold) + Expect(ok).To(BeTrue()) + Expect(s.fs).NotTo(BeNil()) + Expect(s.injector.config).To(BeNil()) + Expect(s.injector.boilerplate).To(Equal("")) + Expect(s.injector.resource).NotTo(BeNil()) + Expect(s.injector.resource.GVK.IsEqualTo(res.GVK)).To(BeTrue()) }) }) @@ -112,14 +93,14 @@ var _ = Describe("Scaffold", func() { }) DescribeTable("successes", - func(expected string, files ...file.Builder) { + func(expected string, files ...Builder) { s := &scaffold{ fs: filesystem.NewMock( filesystem.MockOutput(&output), ), } - Expect(s.Execute(model.NewUniverse(), files...)).To(Succeed()) + Expect(s.Execute(files...)).To(Succeed()) Expect(output.String()).To(Equal(expected)) }, Entry("should write the file", @@ -134,7 +115,7 @@ var _ = Describe("Scaffold", func() { Entry("should overwrite required models if already have one", fileContent, fakeTemplate{}, - fakeTemplate{fakeBuilder: fakeBuilder{ifExistsAction: file.Overwrite}, body: fileContent}, + fakeTemplate{fakeBuilder: fakeBuilder{ifExistsAction: OverwriteFile}, body: fileContent}, ), Entry("should format a go file", "package file\n", @@ -143,26 +124,28 @@ var _ = Describe("Scaffold", func() { ) DescribeTable("file builders related errors", - func(f func(error) bool, files ...file.Builder) { + func(errType error, files ...Builder) { s := &scaffold{fs: filesystem.NewMock()} - Expect(f(s.Execute(model.NewUniverse(), files...))).To(BeTrue()) + err := s.Execute(files...) + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, &errType)).To(BeTrue()) }, Entry("should fail if unable to validate a file builder", - file.IsValidateError, + ValidateError{}, fakeRequiresValidation{validateErr: testErr}, ), Entry("should fail if unable to set default values for a template", - file.IsSetTemplateDefaultsError, + SetTemplateDefaultsError{}, fakeTemplate{err: testErr}, ), Entry("should fail if an unexpected previous model is found", - IsModelAlreadyExistsError, + ModelAlreadyExistsError{}, fakeTemplate{fakeBuilder: fakeBuilder{path: "filename"}}, - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: file.Error}}, + fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: Error}}, ), Entry("should fail if behavior if file exists is not defined", - IsUnknownIfExistsActionError, + UnknownIfExistsActionError{}, fakeTemplate{fakeBuilder: fakeBuilder{path: "filename"}}, fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: -1}}, ), @@ -170,10 +153,10 @@ var _ = Describe("Scaffold", func() { // Following errors are unwrapped, so we need to check for substrings DescribeTable("template related errors", - func(errMsg string, files ...file.Builder) { + func(errMsg string, files ...Builder) { s := &scaffold{fs: filesystem.NewMock()} - err := s.Execute(model.NewUniverse(), files...) + err := s.Execute(files...) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring(errMsg)) }, @@ -192,7 +175,7 @@ var _ = Describe("Scaffold", func() { ) DescribeTable("insert strings", - func(input, expected string, files ...file.Builder) { + func(input, expected string, files ...Builder) { s := &scaffold{ fs: filesystem.NewMock( filesystem.MockInput(bytes.NewBufferString(input)), @@ -201,7 +184,7 @@ var _ = Describe("Scaffold", func() { ), } - Expect(s.Execute(model.NewUniverse(), files...)).To(Succeed()) + Expect(s.Execute(files...)).To(Succeed()) Expect(output.String()).To(Equal(expected)) }, Entry("should insert lines for go files", @@ -213,8 +196,8 @@ var _ = Describe("Scaffold", func() { 2 //+kubebuilder:scaffold:- `, - fakeInserter{codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}}, + fakeInserter{codeFragments: CodeFragmentsMap{ + NewMarkerFor("file.go", "-"): {"1\n", "2\n"}}, }, ), Entry("should insert lines for yaml files", @@ -226,8 +209,8 @@ var _ = Describe("Scaffold", func() { 2 #+kubebuilder:scaffold:- `, - fakeInserter{codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.yaml", "-"): {"1\n", "2\n"}}, + fakeInserter{codeFragments: CodeFragmentsMap{ + NewMarkerFor("file.yaml", "-"): {"1\n", "2\n"}}, }, ), Entry("should use models if there is no file", @@ -237,11 +220,11 @@ var _ = Describe("Scaffold", func() { 2 //+kubebuilder:scaffold:- `, - fakeTemplate{fakeBuilder: fakeBuilder{ifExistsAction: file.Overwrite}, body: ` + fakeTemplate{fakeBuilder: fakeBuilder{ifExistsAction: OverwriteFile}, body: ` //+kubebuilder:scaffold:- `}, - fakeInserter{codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}}, + fakeInserter{codeFragments: CodeFragmentsMap{ + NewMarkerFor("file.go", "-"): {"1\n", "2\n"}}, }, ), Entry("should use required models over files", @@ -251,11 +234,11 @@ var _ = Describe("Scaffold", func() { 2 //+kubebuilder:scaffold:- `, - fakeTemplate{fakeBuilder: fakeBuilder{ifExistsAction: file.Overwrite}, body: ` + fakeTemplate{fakeBuilder: fakeBuilder{ifExistsAction: OverwriteFile}, body: ` //+kubebuilder:scaffold:- `}, - fakeInserter{codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}}, + fakeInserter{codeFragments: CodeFragmentsMap{ + NewMarkerFor("file.go", "-"): {"1\n", "2\n"}}, }, ), Entry("should use files over optional models", @@ -269,8 +252,8 @@ var _ = Describe("Scaffold", func() { `, fakeTemplate{body: fileContent}, fakeInserter{ - codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}, + codeFragments: CodeFragmentsMap{ + NewMarkerFor("file.go", "-"): {"1\n", "2\n"}, }, }, ), @@ -286,10 +269,10 @@ var _ = Describe("Scaffold", func() { //+kubebuilder:scaffold:* `, fakeInserter{ - markers: []file.Marker{file.NewMarkerFor("file.go", "-")}, - codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}, - file.NewMarkerFor("file.go", "*"): {"3\n", "4\n"}, + markers: []Marker{NewMarkerFor("file.go", "-")}, + codeFragments: CodeFragmentsMap{ + NewMarkerFor("file.go", "-"): {"1\n", "2\n"}, + NewMarkerFor("file.go", "*"): {"3\n", "4\n"}, }, }, ), @@ -310,9 +293,9 @@ var _ = Describe("Scaffold", func() { //+kubebuilder:scaffold:* `, fakeInserter{ - codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}, - file.NewMarkerFor("file.go", "*"): {"3\n", "4\n"}, + codeFragments: CodeFragmentsMap{ + NewMarkerFor("file.go", "-"): {"1\n", "2\n"}, + NewMarkerFor("file.go", "*"): {"3\n", "4\n"}, }, }, ), @@ -325,51 +308,37 @@ var _ = Describe("Scaffold", func() { //+kubebuilder:scaffold:- `}, fakeInserter{ - codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {}, + codeFragments: CodeFragmentsMap{ + NewMarkerFor("file.go", "-"): {}, }, }, ), ) DescribeTable("insert strings related errors", - func(f func(error) bool, files ...file.Builder) { + func(errType error, files ...Builder) { s := &scaffold{ fs: filesystem.NewMock( filesystem.MockExists(func(_ string) bool { return true }), ), } - err := s.Execute(model.NewUniverse(), files...) + err := s.Execute(files...) Expect(err).To(HaveOccurred()) - Expect(f(err)).To(BeTrue()) + Expect(errors.As(err, &errType)).To(BeTrue()) }, Entry("should fail if inserting into a model that fails when a file exists and it does exist", - IsFileAlreadyExistsError, - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: file.Error}}, + FileAlreadyExistsError{}, + fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: Error}}, fakeInserter{fakeBuilder: fakeBuilder{path: "filename"}}, ), Entry("should fail if inserting into a model with unknown behavior if the file exists and it does exist", - IsUnknownIfExistsActionError, + UnknownIfExistsActionError{}, fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: -1}}, fakeInserter{fakeBuilder: fakeBuilder{path: "filename"}}, ), ) - It("should fail if a plugin fails", func() { - s := &scaffold{ - fs: filesystem.NewMock(), - plugins: []model.Plugin{fakePlugin{err: testErr}}, - } - - err := s.Execute( - model.NewUniverse(), - fakeTemplate{}, - ) - Expect(err).To(MatchError(testErr)) - Expect(model.IsPluginError(err)).To(BeTrue()) - }) - Context("write when the file already exists", func() { var s Scaffold @@ -383,28 +352,22 @@ var _ = Describe("Scaffold", func() { }) It("should skip the file by default", func() { - Expect(s.Execute( - model.NewUniverse(), - fakeTemplate{body: fileContent}, - )).To(Succeed()) + Expect(s.Execute(fakeTemplate{body: fileContent})).To(Succeed()) Expect(output.String()).To(BeEmpty()) }) It("should write the file if configured to do so", func() { - Expect(s.Execute( - model.NewUniverse(), - fakeTemplate{fakeBuilder: fakeBuilder{ifExistsAction: file.Overwrite}, body: fileContent}, - )).To(Succeed()) + Expect(s.Execute(fakeTemplate{ + fakeBuilder: fakeBuilder{ifExistsAction: OverwriteFile}, + body: fileContent, + })).To(Succeed()) Expect(output.String()).To(Equal(fileContent)) }) It("should error if configured to do so", func() { - err := s.Execute( - model.NewUniverse(), - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: file.Error}, body: fileContent}, - ) + err := s.Execute(fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: Error}, body: fileContent}) Expect(err).To(HaveOccurred()) - Expect(IsFileAlreadyExistsError(err)).To(BeTrue()) + Expect(errors.As(err, &FileAlreadyExistsError{})).To(BeTrue()) Expect(output.String()).To(BeEmpty()) }) }) @@ -413,7 +376,7 @@ var _ = Describe("Scaffold", func() { func( mockErrorF func(error) filesystem.MockOptions, checkErrorF func(error) bool, - files ...file.Builder, + files ...Builder, ) { s := &scaffold{ fs: filesystem.NewMock( @@ -421,7 +384,7 @@ var _ = Describe("Scaffold", func() { ), } - err := s.Execute(model.NewUniverse(), files...) + err := s.Execute(files...) Expect(err).To(HaveOccurred()) Expect(checkErrorF(err)).To(BeTrue()) }, @@ -466,24 +429,12 @@ var _ = Describe("Scaffold", func() { }) }) -var _ model.Plugin = fakePlugin{} - -// fakePlugin is used to mock a model.Plugin in order to test Scaffold -type fakePlugin struct { - err error -} - -// Pipe implements model.Plugin -func (f fakePlugin) Pipe(_ *model.Universe) error { - return f.err -} - -var _ file.Builder = fakeBuilder{} +var _ Builder = fakeBuilder{} // fakeBuilder is used to mock a file.Builder type fakeBuilder struct { path string - ifExistsAction file.IfExistsAction + ifExistsAction IfExistsAction } // GetPath implements file.Builder @@ -492,11 +443,11 @@ func (f fakeBuilder) GetPath() string { } // GetIfExistsAction implements file.Builder -func (f fakeBuilder) GetIfExistsAction() file.IfExistsAction { +func (f fakeBuilder) GetIfExistsAction() IfExistsAction { return f.ifExistsAction } -var _ file.RequiresValidation = fakeRequiresValidation{} +var _ RequiresValidation = fakeRequiresValidation{} // fakeRequiresValidation is used to mock a file.RequiresValidation in order to test Scaffold type fakeRequiresValidation struct { @@ -510,7 +461,7 @@ func (f fakeRequiresValidation) Validate() error { return f.validateErr } -var _ file.Template = fakeTemplate{} +var _ Template = fakeTemplate{} // fakeTemplate is used to mock a file.File in order to test Scaffold type fakeTemplate struct { @@ -537,17 +488,17 @@ func (f fakeTemplate) SetTemplateDefaults() error { type fakeInserter struct { fakeBuilder - markers []file.Marker - codeFragments file.CodeFragmentsMap + markers []Marker + codeFragments CodeFragmentsMap } // GetMarkers implements file.UpdatableTemplate -func (f fakeInserter) GetMarkers() []file.Marker { +func (f fakeInserter) GetMarkers() []Marker { if f.markers != nil { return f.markers } - markers := make([]file.Marker, 0, len(f.codeFragments)) + markers := make([]Marker, 0, len(f.codeFragments)) for marker := range f.codeFragments { markers = append(markers, marker) } @@ -555,6 +506,6 @@ func (f fakeInserter) GetMarkers() []file.Marker { } // GetCodeFragments implements file.UpdatableTemplate -func (f fakeInserter) GetCodeFragments() file.CodeFragmentsMap { +func (f fakeInserter) GetCodeFragments() CodeFragmentsMap { return f.codeFragments } diff --git a/pkg/plugins/golang/v2/suite_test.go b/pkg/machinery/suite_test.go similarity index 88% rename from pkg/plugins/golang/v2/suite_test.go rename to pkg/machinery/suite_test.go index de06fb4cf53..becbe2ed2d4 100644 --- a/pkg/plugins/golang/v2/suite_test.go +++ b/pkg/machinery/suite_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v2 +package machinery import ( "testing" @@ -23,7 +23,7 @@ import ( . "github.com/onsi/gomega" ) -func TestGoPluginV2(t *testing.T) { +func TestMachinery(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Go Plugin v2 Suite") + RunSpecs(t, "Machinery suite") } diff --git a/pkg/plugins/internal/util/exec.go b/pkg/machinery/util/exec.go similarity index 100% rename from pkg/plugins/internal/util/exec.go rename to pkg/machinery/util/exec.go diff --git a/pkg/plugins/internal/util/go_version.go b/pkg/machinery/util/go_version.go similarity index 94% rename from pkg/plugins/internal/util/go_version.go rename to pkg/machinery/util/go_version.go index fb71f7589cb..2a769cde048 100644 --- a/pkg/plugins/internal/util/go_version.go +++ b/pkg/machinery/util/go_version.go @@ -28,7 +28,7 @@ import ( func ValidateGoVersion() error { err := fetchAndCheckGoVersion() if err != nil { - return fmt.Errorf("%s. You can skip this check using the --skip-go-version-check flag", err) + return fmt.Errorf("%w. You can skip this check using the --skip-go-version-check flag", err) } return nil } @@ -46,7 +46,7 @@ func fetchAndCheckGoVersion() error { } goVer := split[2] if err := checkGoVersion(goVer); err != nil { - return fmt.Errorf("go version '%s' is incompatible because '%s'", goVer, err) + return fmt.Errorf("go version '%s' is incompatible because '%w'", goVer, err) } return nil } diff --git a/pkg/plugins/internal/util/go_version_test.go b/pkg/machinery/util/go_version_test.go similarity index 100% rename from pkg/plugins/internal/util/go_version_test.go rename to pkg/machinery/util/go_version_test.go diff --git a/pkg/plugins/internal/util/repository.go b/pkg/machinery/util/repository.go similarity index 100% rename from pkg/plugins/internal/util/repository.go rename to pkg/machinery/util/repository.go diff --git a/pkg/plugins/internal/util/stdin.go b/pkg/machinery/util/stdin.go similarity index 100% rename from pkg/plugins/internal/util/stdin.go rename to pkg/machinery/util/stdin.go diff --git a/pkg/model/file/errors.go b/pkg/model/file/errors.go deleted file mode 100644 index 20349f55844..00000000000 --- a/pkg/model/file/errors.go +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package file - -import ( - "errors" -) - -// validateError is a wrapper error that will be used for errors returned by RequiresValidation.Validate -type validateError struct { - error -} - -// NewValidateError wraps an error to specify it was returned by RequiresValidation.Validate -func NewValidateError(err error) error { - return validateError{err} -} - -// Unwrap implements Wrapper interface -func (e validateError) Unwrap() error { - return e.error -} - -// IsValidateError checks if the error was returned by RequiresValidation.Validate -func IsValidateError(err error) bool { - return errors.As(err, &validateError{}) -} - -// setTemplateDefaultsError is a wrapper error that will be used for errors returned by Template.SetTemplateDefaults -type setTemplateDefaultsError struct { - error -} - -// NewSetTemplateDefaultsError wraps an error to specify it was returned by Template.SetTemplateDefaults -func NewSetTemplateDefaultsError(err error) error { - return setTemplateDefaultsError{err} -} - -// Unwrap implements Wrapper interface -func (e setTemplateDefaultsError) Unwrap() error { - return e.error -} - -// IsSetTemplateDefaultsError checks if the error was returned by Template.SetTemplateDefaults -func IsSetTemplateDefaultsError(err error) bool { - return errors.As(err, &setTemplateDefaultsError{}) -} diff --git a/pkg/model/plugin.go b/pkg/model/plugin.go deleted file mode 100644 index 57d326fa378..00000000000 --- a/pkg/model/plugin.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package model - -import ( - "errors" -) - -// Plugin is the interface that a plugin must implement -// We will (later) have an ExecPlugin that implements this by exec-ing a binary -type Plugin interface { - // Pipe is the core plugin interface, that transforms a UniverseModel - Pipe(*Universe) error -} - -// pluginError is a wrapper error that will be used for errors returned by Plugin.Pipe -type pluginError struct { - error -} - -// NewPluginError wraps an error to specify it was returned by Plugin.Pipe -func NewPluginError(err error) error { - return pluginError{err} -} - -// Unwrap implements Wrapper interface -func (e pluginError) Unwrap() error { - return e.error -} - -// IsPluginError checks if the error was returned by Plugin.Pipe -func IsPluginError(err error) bool { - return errors.As(err, &pluginError{}) -} diff --git a/pkg/model/resource/resource_test.go b/pkg/model/resource/resource_test.go index 0d1151e8efc..fcd5364720c 100644 --- a/pkg/model/resource/resource_test.go +++ b/pkg/model/resource/resource_test.go @@ -66,6 +66,8 @@ var _ = Describe("Resource", func() { safeDomain = "testio" groupVersion = group + version domainVersion = safeDomain + version + safeGroup = "mygroup" + safeAlias = safeGroup + version ) var ( @@ -85,6 +87,22 @@ var _ = Describe("Resource", func() { Kind: kind, }, } + resHyphenGroup = Resource{ + GVK: GVK{ + Group: "my-group", + Domain: domain, + Version: version, + Kind: kind, + }, + } + resDotGroup = Resource{ + GVK: GVK{ + Group: "my.group", + Domain: domain, + Version: version, + Kind: kind, + }, + } ) DescribeTable("PackageName should return the correct string", @@ -92,6 +110,8 @@ var _ = Describe("Resource", func() { Entry("fully qualified resource", res, group), Entry("empty group name", resNoGroup, safeDomain), Entry("empty domain", resNoDomain, group), + Entry("hyphen-containing group", resHyphenGroup, safeGroup), + Entry("dot-containing group", resDotGroup, safeGroup), ) DescribeTable("ImportAlias", @@ -99,6 +119,8 @@ var _ = Describe("Resource", func() { Entry("fully qualified resource", res, groupVersion), Entry("empty group name", resNoGroup, domainVersion), Entry("empty domain", resNoDomain, groupVersion), + Entry("hyphen-containing group", resHyphenGroup, safeAlias), + Entry("dot-containing group", resDotGroup, safeAlias), ) }) @@ -284,6 +306,17 @@ var _ = Describe("Resource", func() { Expect(r.Update(other)).NotTo(Succeed()) }) + It("should work for a new path", func() { + const path = "api/v1" + r = Resource{GVK: gvk} + other = Resource{ + GVK: gvk, + Path: path, + } + Expect(r.Update(other)).To(Succeed()) + Expect(r.Path).To(Equal(path)) + }) + It("should fail for different Paths", func() { r = Resource{ GVK: gvk, diff --git a/pkg/model/universe.go b/pkg/model/universe.go deleted file mode 100644 index 15d597c88f7..00000000000 --- a/pkg/model/universe.go +++ /dev/null @@ -1,111 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package model - -import ( - "sigs.k8s.io/kubebuilder/v3/pkg/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" - "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" -) - -// Universe describes the entire state of file generation -type Universe struct { - // Config stores the project configuration - Config config.Config `json:"config,omitempty"` - - // Boilerplate is the copyright comment added at the top of scaffolded files - Boilerplate string `json:"boilerplate,omitempty"` - - // Resource contains the information of the API that is being scaffolded - Resource *resource.Resource `json:"resource,omitempty"` - - // Files contains the model of the files that are being scaffolded - Files map[string]*file.File `json:"files,omitempty"` -} - -// NewUniverse creates a new Universe -func NewUniverse(options ...UniverseOption) *Universe { - universe := &Universe{} - - // Apply options - for _, option := range options { - option(universe) - } - - return universe -} - -// UniverseOption configure Universe -type UniverseOption func(*Universe) - -// WithConfig stores the already loaded project configuration -func WithConfig(projectConfig config.Config) UniverseOption { - return func(universe *Universe) { - universe.Config = projectConfig - } -} - -// WithBoilerplate stores the already loaded project configuration -func WithBoilerplate(boilerplate string) UniverseOption { - return func(universe *Universe) { - universe.Boilerplate = boilerplate - } -} - -// WithoutBoilerplate is used for files that do not require a boilerplate -func WithoutBoilerplate(universe *Universe) { - universe.Boilerplate = "" -} - -// WithResource stores the provided resource -func WithResource(resource *resource.Resource) UniverseOption { - return func(universe *Universe) { - universe.Resource = resource - } -} - -// InjectInto injects fields from the universe into the builder -func (u Universe) InjectInto(builder file.Builder) { - // Inject project configuration - if u.Config != nil { - if builderWithDomain, hasDomain := builder.(file.HasDomain); hasDomain { - builderWithDomain.InjectDomain(u.Config.GetDomain()) - } - if builderWithRepository, hasRepository := builder.(file.HasRepository); hasRepository { - builderWithRepository.InjectRepository(u.Config.GetRepository()) - } - if builderWithProjectName, hasProjectName := builder.(file.HasProjectName); hasProjectName { - builderWithProjectName.InjectProjectName(u.Config.GetProjectName()) - } - if builderWithMultiGroup, hasMultiGroup := builder.(file.HasMultiGroup); hasMultiGroup { - builderWithMultiGroup.InjectMultiGroup(u.Config.IsMultiGroup()) - } - if builderWithComponentConfig, hasComponentConfig := builder.(file.HasComponentConfig); hasComponentConfig { - builderWithComponentConfig.InjectComponentConfig(u.Config.IsComponentConfig()) - } - } - // Inject boilerplate - if builderWithBoilerplate, hasBoilerplate := builder.(file.HasBoilerplate); hasBoilerplate { - builderWithBoilerplate.InjectBoilerplate(u.Boilerplate) - } - // Inject resource - if u.Resource != nil { - if builderWithResource, hasResource := builder.(file.HasResource); hasResource { - builderWithResource.InjectResource(u.Resource) - } - } -} diff --git a/pkg/plugin/errors.go b/pkg/plugin/errors.go new file mode 100644 index 00000000000..4297ddca6ab --- /dev/null +++ b/pkg/plugin/errors.go @@ -0,0 +1,32 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +import ( + "fmt" +) + +// ExitError is a typed error that is returned by a plugin when no further steps should be executed for itself. +type ExitError struct { + Plugin string + Reason string +} + +// Error implements error +func (e ExitError) Error() string { + return fmt.Sprintf("plugin %q exit early: %s", e.Plugin, e.Reason) +} diff --git a/plugins/addon/channel.go b/pkg/plugin/errors_test.go similarity index 51% rename from plugins/addon/channel.go rename to pkg/plugin/errors_test.go index f4804e5d528..5d295b8212c 100644 --- a/plugins/addon/channel.go +++ b/pkg/plugin/errors_test.go @@ -1,5 +1,5 @@ /* -Copyright 2019 The Kubernetes Authors. +Copyright 2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,28 +14,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -package addon +package plugin import ( - "path/filepath" - - "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" ) -const exampleChannel = `# Versions for the stable channel -manifests: -- version: 0.0.1 -` - -// ExampleChannel adds a model file for the channel -func ExampleChannel(u *model.Universe) error { - m := &file.File{ - Path: filepath.Join("channels", "stable"), - Contents: exampleChannel, - IfExistsAction: file.Skip, +var _ = Describe("PluginKeyNotFoundError", func() { + var err = ExitError{ + Plugin: "go.kubebuilder.io/v1", + Reason: "skipping plugin", } - _, err := AddFile(u, m) - return err -} + Context("Error", func() { + It("should return the correct error message", func() { + Expect(err.Error()).To(Equal("plugin \"go.kubebuilder.io/v1\" exit early: skipping plugin")) + }) + }) +}) diff --git a/pkg/plugin/helpers.go b/pkg/plugin/helpers.go index 3a2d43f4e4c..e9e7aa68691 100644 --- a/pkg/plugin/helpers.go +++ b/pkg/plugin/helpers.go @@ -25,17 +25,9 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" ) -// 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 Plugin's unique identifying string. func KeyFor(p Plugin) string { - return Key(p.Name(), p.Version().String()) + return path.Join(p.Name(), p.Version().String()) } // SplitKey returns a name and version for a plugin key. diff --git a/pkg/plugin/helpers_test.go b/pkg/plugin/helpers_test.go new file mode 100644 index 00000000000..0a2856b1b5a --- /dev/null +++ b/pkg/plugin/helpers_test.go @@ -0,0 +1,147 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/stage" +) + +type mockPlugin struct { + name string + version Version + supportedProjectVersions []config.Version +} + +func (p mockPlugin) Name() string { return p.name } +func (p mockPlugin) Version() Version { return p.version } +func (p mockPlugin) SupportedProjectVersions() []config.Version { return p.supportedProjectVersions } + +const ( + short = "go" + name = "go.kubebuilder.io" + key = "go.kubebuilder.io/v1" +) + +var ( + version = Version{Number: 1} + supportedProjectVersions = []config.Version{ + {Number: 2}, + {Number: 3}, + } +) + +var _ = Describe("KeyFor", func() { + It("should join plugins name and version", func() { + plugin := mockPlugin{ + name: name, + version: version, + } + Expect(KeyFor(plugin)).To(Equal(key)) + }) +}) + +var _ = Describe("SplitKey", func() { + It("should split keys with versions", func() { + n, v := SplitKey(key) + Expect(n).To(Equal(name)) + Expect(v).To(Equal(version.String())) + }) + + It("should split keys without versions", func() { + n, v := SplitKey(name) + Expect(n).To(Equal(name)) + Expect(v).To(Equal("")) + }) +}) + +var _ = Describe("GetShortName", func() { + It("should extract base names from domains", func() { + Expect(GetShortName(name)).To(Equal(short)) + }) +}) + +var _ = Describe("Validate", func() { + It("should succeed for valid plugins", func() { + plugin := mockPlugin{ + name: name, + version: version, + supportedProjectVersions: supportedProjectVersions, + } + Expect(Validate(plugin)).To(Succeed()) + }) + + DescribeTable("should fail", + func(plugin Plugin) { + Expect(Validate(plugin)).NotTo(Succeed()) + }, + Entry("for invalid plugin names", mockPlugin{ + name: "go_kubebuilder.io", + version: version, + supportedProjectVersions: supportedProjectVersions, + }), + Entry("for invalid plugin versions", mockPlugin{ + name: name, + version: Version{Number: -1}, + supportedProjectVersions: supportedProjectVersions, + }), + Entry("for no supported project version", mockPlugin{ + name: name, + version: version, + supportedProjectVersions: nil, + }), + Entry("for invalid supported project version", mockPlugin{ + name: name, + version: version, + supportedProjectVersions: []config.Version{{Number: -1}}, + }), + ) +}) + +var _ = Describe("ValidateKey", func() { + It("should succeed for valid keys", func() { + Expect(ValidateKey(key)).To(Succeed()) + }) + + DescribeTable("should fail", + func(key string) { + Expect(ValidateKey(key)).NotTo(Succeed()) + }, + Entry("for invalid plugin names", "go_kubebuilder.io/v1"), + Entry("for invalid versions", "go.kubebuilder.io/a"), + ) +}) + +var _ = Describe("SupportsVersion", func() { + plugin := mockPlugin{ + supportedProjectVersions: supportedProjectVersions, + } + + It("should return true for supported versions", func() { + Expect(SupportsVersion(plugin, config.Version{Number: 2})).To(BeTrue()) + Expect(SupportsVersion(plugin, config.Version{Number: 3})).To(BeTrue()) + }) + + It("should return false for non-supported versions", func() { + Expect(SupportsVersion(plugin, config.Version{Number: 1})).To(BeFalse()) + Expect(SupportsVersion(plugin, config.Version{Number: 3, Stage: stage.Alpha})).To(BeFalse()) + }) +}) diff --git a/pkg/plugin/metadata.go b/pkg/plugin/metadata.go new file mode 100644 index 00000000000..5a83d8bdf2c --- /dev/null +++ b/pkg/plugin/metadata.go @@ -0,0 +1,31 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +// CLIMetadata is the runtime meta-data of the CLI +type CLIMetadata struct { + // CommandName is the root command name. + CommandName string +} + +// SubcommandMetadata is the runtime meta-data for a subcommand +type SubcommandMetadata struct { + // Description is a description of what this command does. It is used to display help. + Description string + // Examples are one or more examples of the command-line usage of this command. It is used to display help. + Examples string +} diff --git a/pkg/plugin/interfaces.go b/pkg/plugin/plugin.go similarity index 60% rename from pkg/plugin/interfaces.go rename to pkg/plugin/plugin.go index 1c1cf2da01a..d8a04ee8f02 100644 --- a/pkg/plugin/interfaces.go +++ b/pkg/plugin/plugin.go @@ -17,8 +17,6 @@ limitations under the License. package plugin import ( - "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/v3/pkg/config" ) @@ -43,32 +41,6 @@ type Deprecated interface { DeprecationWarning() string } -// Subcommand is an interface that defines the common base for subcommands returned by plugins -type Subcommand interface { - // UpdateContext updates a Context with subcommand-specific help text, like description and examples. It also serves - // to pass context from the CLI to the subcommand, such as the command name. - // Can be a no-op if default help text is desired. - UpdateContext(*Context) - // BindFlags binds the subcommand's flags to the CLI. This allows each subcommand to define its own - // command line flags. - BindFlags(*pflag.FlagSet) - // Run runs the subcommand. - Run() error - // InjectConfig passes a config to a plugin. The plugin may modify the config. - // Initializing, loading, and saving the config is managed by the cli package. - InjectConfig(config.Config) -} - -// Context is the runtime context for a subcommand. -type Context struct { - // CommandName sets the command name for a subcommand. - CommandName string - // Description is a description of what this subcommand does. It is used to display help. - Description string - // Examples are one or more examples of the command-line usage of this subcommand. It is used to display help. - Examples string -} - // Init is an interface for plugins that provide an `init` subcommand type Init interface { Plugin @@ -76,11 +48,6 @@ type Init interface { GetInitSubcommand() InitSubcommand } -// InitSubcommand is an interface that represents an `init` subcommand -type InitSubcommand interface { - Subcommand -} - // CreateAPI is an interface for plugins that provide a `create api` subcommand type CreateAPI interface { Plugin @@ -88,11 +55,6 @@ type CreateAPI interface { GetCreateAPISubcommand() CreateAPISubcommand } -// CreateAPISubcommand is an interface that represents a `create api` subcommand -type CreateAPISubcommand interface { - Subcommand -} - // CreateWebhook is an interface for plugins that provide a `create webhook` subcommand type CreateWebhook interface { Plugin @@ -100,11 +62,6 @@ type CreateWebhook interface { GetCreateWebhookSubcommand() CreateWebhookSubcommand } -// CreateWebhookSubcommand is an interface that represents a `create wekbhook` subcommand -type CreateWebhookSubcommand interface { - Subcommand -} - // Edit is an interface for plugins that provide a `edit` subcommand type Edit interface { Plugin @@ -112,11 +69,6 @@ type Edit interface { GetEditSubcommand() EditSubcommand } -// EditSubcommand is an interface that represents an `edit` subcommand -type EditSubcommand interface { - Subcommand -} - // Full is an interface for plugins that provide `init`, `create api`, `create webhook` and `edit` subcommands type Full interface { Init diff --git a/pkg/plugin/subcommand.go b/pkg/plugin/subcommand.go new file mode 100644 index 00000000000..483c018d3e0 --- /dev/null +++ b/pkg/plugin/subcommand.go @@ -0,0 +1,94 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +import ( + "github.com/spf13/afero" + "github.com/spf13/pflag" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +// UpdatesMetadata is an interface that implements the optional metadata update method. +type UpdatesMetadata interface { + // UpdateMetadata updates the subcommand metadata. + UpdateMetadata(CLIMetadata, *SubcommandMetadata) +} + +// HasFlags is an interface that implements the optional bind flags method. +type HasFlags interface { + // BindFlags binds flags to the CLI subcommand. + BindFlags(*pflag.FlagSet) +} + +// RequiresConfig is an interface that implements the optional inject config method. +type RequiresConfig interface { + // InjectConfig injects the configuration to a subcommand. + InjectConfig(config.Config) error +} + +// RequiresResource is an interface that implements the required inject resource method. +type RequiresResource interface { + // InjectResource injects the resource model to a subcommand. + InjectResource(*resource.Resource) error +} + +// HasPreScaffold is an interface that implements the optional pre-scaffold method. +type HasPreScaffold interface { + // PreScaffold executes tasks before the main scaffolding. + PreScaffold(afero.Fs) error +} + +// Scaffolder is an interface that implements the required scaffold method. +type Scaffolder interface { + // Scaffold implements the main scaffolding. + Scaffold(afero.Fs) error +} + +// HasPostScaffold is an interface that implements the optional post-scaffold method. +type HasPostScaffold interface { + // PostScaffold executes tasks after the main scaffolding. + PostScaffold() error +} + +// Subcommand is a base interface for all subcommands. +type Subcommand interface { + Scaffolder +} + +// InitSubcommand is an interface that represents an `init` subcommand. +type InitSubcommand interface { + Subcommand +} + +// CreateAPISubcommand is an interface that represents a `create api` subcommand. +type CreateAPISubcommand interface { + Subcommand + RequiresResource +} + +// CreateWebhookSubcommand is an interface that represents a `create wekbhook` subcommand. +type CreateWebhookSubcommand interface { + Subcommand + RequiresResource +} + +// EditSubcommand is an interface that represents an `edit` subcommand. +type EditSubcommand interface { + Subcommand +} diff --git a/pkg/cli/internal/config/config_suite_test.go b/pkg/plugin/suite_test.go similarity index 89% rename from pkg/cli/internal/config/config_suite_test.go rename to pkg/plugin/suite_test.go index fffe2f8bb2f..35484a744c7 100644 --- a/pkg/cli/internal/config/config_suite_test.go +++ b/pkg/plugin/suite_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package config +package plugin import ( "testing" @@ -23,7 +23,7 @@ import ( . "github.com/onsi/gomega" ) -func TestCLI(t *testing.T) { +func TestPlugin(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Config Suite") + RunSpecs(t, "Plugin Suite") } diff --git a/pkg/plugin/version_test.go b/pkg/plugin/version_test.go index f1512dbf7d8..610676906aa 100644 --- a/pkg/plugin/version_test.go +++ b/pkg/plugin/version_test.go @@ -18,22 +18,16 @@ package plugin import ( "sort" - "testing" - g "github.com/onsi/ginkgo" // An alias is required because Context is defined elsewhere in this package. + . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" "sigs.k8s.io/kubebuilder/v3/pkg/model/stage" ) -func TestPlugin(t *testing.T) { - RegisterFailHandler(g.Fail) - g.RunSpecs(t, "Plugin Suite") -} - -var _ = g.Describe("Version", func() { - g.Context("Parse", func() { +var _ = Describe("Version", func() { + Context("Parse", func() { DescribeTable("should be correctly parsed for valid version strings", func(str string, number int, s stage.Stage) { var v Version @@ -72,7 +66,7 @@ var _ = g.Describe("Version", func() { ) }) - g.Context("String", func() { + Context("String", func() { DescribeTable("should return the correct string value", func(version Version, str string) { Expect(version.String()).To(Equal(str)) }, Entry("for version 0", Version{Number: 0}, "v0"), @@ -94,7 +88,7 @@ var _ = g.Describe("Version", func() { ) }) - g.Context("Validate", func() { + Context("Validate", func() { DescribeTable("should validate valid versions", func(version Version) { Expect(version.Validate()).To(Succeed()) }, Entry("for version 0", Version{Number: 0}), @@ -125,7 +119,7 @@ var _ = g.Describe("Version", func() { ) }) - g.Context("Compare", func() { + Context("Compare", func() { // Test Compare() by sorting a list. var ( versions = []Version{ @@ -155,7 +149,7 @@ var _ = g.Describe("Version", func() { } ) - g.It("sorts a valid list of versions correctly", func() { + It("sorts a valid list of versions correctly", func() { sort.Slice(versions, func(i int, j int) bool { return versions[i].Compare(versions[j]) == -1 }) @@ -164,7 +158,7 @@ var _ = g.Describe("Version", func() { }) - g.Context("IsStable", func() { + Context("IsStable", func() { DescribeTable("should return true for stable versions", func(version Version) { Expect(version.IsStable()).To(BeTrue()) }, Entry("for version 1", Version{Number: 1}), @@ -189,4 +183,5 @@ var _ = g.Describe("Version", func() { Entry("for version 22 (beta)", Version{Number: 22, Stage: stage.Beta}), ) }) + }) diff --git a/pkg/plugins/declarative/v1/api.go b/pkg/plugins/declarative/v1/api.go new file mode 100644 index 00000000000..9907fd485eb --- /dev/null +++ b/pkg/plugins/declarative/v1/api.go @@ -0,0 +1,112 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "fmt" + "path/filepath" + "strings" + + "github.com/spf13/afero" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery/util" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/declarative/v1/internal/templates" + goPluginV3 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3" +) + +const ( + // kbDeclarativePattern is the sigs.k8s.io/kubebuilder-declarative-pattern version + kbDeclarativePatternForV2 = "v0.0.0-20200522144838-848d48e5b073" + kbDeclarativePatternForV3 = "v0.0.0-20210113160450-b84d99da0217" + + exampleManifestVersion = "0.0.1" +) + +var _ plugin.CreateAPISubcommand = &createAPISubcommand{} + +type createAPISubcommand struct { + config config.Config + + resource *resource.Resource +} + +func (p *createAPISubcommand) InjectConfig(c config.Config) error { + p.config = c + + return nil +} + +func (p *createAPISubcommand) InjectResource(res *resource.Resource) error { + p.resource = res + + if !p.resource.HasAPI() || !p.resource.HasController() { + return plugin.ExitError{ + Plugin: pluginName, + Reason: "declarative pattern is only supported when API and controller are scaffolded", + } + } + + return nil +} + +func (p *createAPISubcommand) Scaffold(fs afero.Fs) error { + fmt.Println("updating scaffold with declarative pattern...") + + // Load the boilerplate + bp, err := afero.ReadFile(fs, filepath.Join("hack", "boilerplate.go.txt")) + if err != nil { + return fmt.Errorf("error updating scaffold: unable to load boilerplate: %w", err) + } + boilerplate := string(bp) + + // Initialize the machinery.Scaffold that will write the files to disk + scaffold := machinery.NewScaffold(fs, + machinery.WithConfig(p.config), + machinery.WithBoilerplate(boilerplate), + machinery.WithResource(p.resource), + ) + + if err := scaffold.Execute( + &templates.Types{}, + &templates.Controller{}, + &templates.Channel{ManifestVersion: exampleManifestVersion}, + &templates.Manifest{ManifestVersion: exampleManifestVersion}, + ); err != nil { + return fmt.Errorf("error updating scaffold: %w", err) + } + + return nil +} + +func (p *createAPISubcommand) PostScaffold() error { + // Ensure that we are pinning sigs.k8s.io/kubebuilder-declarative-pattern version + kbDeclarativePattern := kbDeclarativePatternForV2 + if strings.Split(p.config.GetLayout(), ",")[0] == plugin.KeyFor(goPluginV3.Plugin{}) { + kbDeclarativePattern = kbDeclarativePatternForV3 + } + err := util.RunCmd("Get controller runtime", "go", "get", + "sigs.k8s.io/kubebuilder-declarative-pattern@"+kbDeclarativePattern) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/plugins/declarative/v1/internal/templates/channel.go b/pkg/plugins/declarative/v1/internal/templates/channel.go new file mode 100644 index 00000000000..9c72df227a7 --- /dev/null +++ b/pkg/plugins/declarative/v1/internal/templates/channel.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package templates + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &Channel{} + +// Channel scaffolds the file for the channel +type Channel struct { + machinery.TemplateMixin + + ManifestVersion string +} + +// SetTemplateDefaults implements file.Template +func (f *Channel) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("channels", "stable") + } + fmt.Println(f.Path) + + f.TemplateBody = channelTemplate + + f.IfExistsAction = machinery.SkipFile + + return nil +} + +const channelTemplate = `# Versions for the stable channel +manifests: +- version: {{ .ManifestVersion }} +` diff --git a/plugins/addon/controller.go b/pkg/plugins/declarative/v1/internal/templates/controller.go similarity index 66% rename from plugins/addon/controller.go rename to pkg/plugins/declarative/v1/internal/templates/controller.go index d84615feafb..31e4f11bbfa 100644 --- a/plugins/addon/controller.go +++ b/pkg/plugins/declarative/v1/internal/templates/controller.go @@ -1,30 +1,52 @@ -package addon +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package templates import ( "path/filepath" - "strings" - "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -// ReplaceController replaces the controller with a modified version -func ReplaceController(u *model.Universe) error { - templateBody := controllerTemplate +var _ machinery.Template = &Controller{} - funcs := DefaultTemplateFunctions() - contents, err := RunTemplate("controller", templateBody, u, funcs) - if err != nil { - return err - } +// Controller scaffolds the file that defines the controller for a CRD or a builtin resource +// nolint:maligned +type Controller struct { + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin +} - m := &file.File{ - Path: filepath.Join("controllers", strings.ToLower(u.Resource.Kind)+"_controller.go"), - Contents: contents, - IfExistsAction: file.Error, +// SetTemplateDefaults implements file.Template +func (f *Controller) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup { + f.Path = filepath.Join("controllers", "%[group]", "%[kind]_controller.go") + } else { + f.Path = filepath.Join("controllers", "%[kind]_controller.go") + } } + f.Path = f.Resource.Replacer().Replace(f.Path) + + f.TemplateBody = controllerTemplate - ReplaceFileIfExists(u, m) + f.IfExistsAction = machinery.OverwriteFile return nil } diff --git a/pkg/plugins/declarative/v1/internal/templates/manifest.go b/pkg/plugins/declarative/v1/internal/templates/manifest.go new file mode 100644 index 00000000000..252af3bf888 --- /dev/null +++ b/pkg/plugins/declarative/v1/internal/templates/manifest.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package templates + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &Manifest{} + +// Manifest scaffolds the file that acts as a placeholder for the manifest +type Manifest struct { + machinery.TemplateMixin + machinery.ResourceMixin + + ManifestVersion string +} + +// SetTemplateDefaults implements file.Template +func (f *Manifest) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("channels", "packages", "%[kind]", f.ManifestVersion, "manifest.yaml") + } + f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) + + f.TemplateBody = manifestTemplate + + f.IfExistsAction = machinery.SkipFile + + return nil +} + +const manifestTemplate = `# Placeholder manifest - replace with the manifest for your addon +` diff --git a/plugins/addon/type.go b/pkg/plugins/declarative/v1/internal/templates/types.go similarity index 63% rename from plugins/addon/type.go rename to pkg/plugins/declarative/v1/internal/templates/types.go index ebcbeab56b7..3a0c2b946c7 100644 --- a/plugins/addon/type.go +++ b/pkg/plugins/declarative/v1/internal/templates/types.go @@ -1,50 +1,67 @@ -package addon +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package templates import ( "fmt" "path/filepath" - "strings" + "text/template" - "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -// ReplaceTypes replaces the API types with a modified version -func ReplaceTypes(u *model.Universe) error { - funcs := DefaultTemplateFunctions() - funcs["JSONTag"] = JSONTag +var _ machinery.Template = &Types{} - contents, err := RunTemplate("types", typesTemplate, u, funcs) - if err != nil { - return err - } +// Types scaffolds the file that defines the schema for a CRD +// nolint:maligned +type Types struct { + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin +} - var path string - if u.Config.IsMultiGroup() { - path = filepath.Join("apis", u.Resource.Version, strings.ToLower(u.Resource.Kind)+"_types.go") - } else { - path = filepath.Join("api", u.Resource.Version, strings.ToLower(u.Resource.Kind)+"_types.go") +// SetTemplateDefaults implements file.Template +func (f *Types) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup { + f.Path = filepath.Join("apis", "%[group]", "%[version]", "%[kind]_types.go") + } else { + f.Path = filepath.Join("api", "%[version]", "%[kind]_types.go") + } } + f.Path = f.Resource.Replacer().Replace(f.Path) - m := &file.File{ - Path: path, - Contents: contents, - IfExistsAction: file.Error, - } + f.TemplateBody = typesTemplate - ReplaceFileIfExists(u, m) + f.IfExistsAction = machinery.OverwriteFile return nil } -// JSONTag is a helper to build the json tag for a struct -// It works around escaping problems for the json tag syntax -func JSONTag(tag string) string { - return fmt.Sprintf("`json:\"%s\"`", tag) +// GetFuncMap implements file.UseCustomFuncMap +func (f Types) GetFuncMap() template.FuncMap { + funcMap := machinery.DefaultFuncMap() + funcMap["JSONTag"] = func(tag string) string { + return fmt.Sprintf("`json:%q`", tag) + } + return funcMap } -// Resource.Resource - const typesTemplate = `{{ .Boilerplate }} package {{ .Resource.Version }} diff --git a/pkg/plugins/declarative/v1/plugin.go b/pkg/plugins/declarative/v1/plugin.go new file mode 100644 index 00000000000..bdcb5d63173 --- /dev/null +++ b/pkg/plugins/declarative/v1/plugin.go @@ -0,0 +1,51 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" + cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" +) + +const pluginName = "declarative" + plugins.DefaultNameQualifier + +var ( + supportedProjectVersions = []config.Version{cfgv2.Version, cfgv3.Version} + pluginVersion = plugin.Version{Number: 1} +) + +var _ plugin.CreateAPI = Plugin{} + +// Plugin implements the plugin.Full interface +type Plugin struct { + createAPISubcommand +} + +// Name returns the name of the plugin +func (Plugin) Name() string { return pluginName } + +// Version returns the version of the plugin +func (Plugin) Version() plugin.Version { return pluginVersion } + +// SupportedProjectVersions returns an array with all project versions supported by the plugin +func (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions } + +// GetCreateAPISubcommand will return the subcommand which is responsible for scaffolding apis +func (p Plugin) GetCreateAPISubcommand() plugin.CreateAPISubcommand { return &p.createAPISubcommand } diff --git a/pkg/plugins/golang/options.go b/pkg/plugins/golang/options.go index 892217da7ca..230dc1c3383 100644 --- a/pkg/plugins/golang/options.go +++ b/pkg/plugins/golang/options.go @@ -17,23 +17,13 @@ limitations under the License. package golang import ( - "fmt" "path" - "strings" - newconfig "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) -const ( - groupPresent = "group flag present but empty" - versionPresent = "version flag present but empty" - kindPresent = "kind flag present but empty" - groupRequired = "group cannot be empty if the domain is empty" - versionRequired = "version cannot be empty" - kindRequired = "kind cannot be empty" -) - var ( coreGroups = map[string]string{ "admission": "k8s.io", @@ -64,17 +54,7 @@ var ( // Options contains the information required to build a new resource.Resource. type Options struct { - // Group is the resource's group. Does not contain the domain. - Group string - // Domain is the resource's domain. - Domain string - // Version is the resource's version. - Version string - // Kind is the resource's kind. - Kind string - // Plural is the resource's kind plural form. - // Optional Plural string // CRDVersion is the CustomResourceDefinition API version that will be used for the resource. @@ -93,82 +73,36 @@ type Options struct { DoConversion bool } -// Validate verifies that all the fields have valid values -func (opts Options) Validate() error { - // Check that the required flags did not get a flag as their value - // We can safely look for a '-' as the first char as none of the fields accepts it - // NOTE: We must do this for all the required flags first or we may output the wrong - // error as flags may seem to be missing because Cobra assigned them to another flag. - if strings.HasPrefix(opts.Group, "-") { - return fmt.Errorf(groupPresent) - } - if strings.HasPrefix(opts.Version, "-") { - return fmt.Errorf(versionPresent) - } - if strings.HasPrefix(opts.Kind, "-") { - return fmt.Errorf(kindPresent) - } - - // Now we can check that all the required flags are not empty - if len(opts.Group) == 0 && len(opts.Domain) == 0 { - return fmt.Errorf(groupRequired) - } - if len(opts.Version) == 0 { - return fmt.Errorf(versionRequired) - } - if len(opts.Kind) == 0 { - return fmt.Errorf(kindRequired) - } - - return nil -} - -// GVK returns the GVK identifier of a resource. -func (opts Options) GVK() resource.GVK { - return resource.GVK{ - Group: opts.Group, - Domain: opts.Domain, - Version: opts.Version, - Kind: opts.Kind, - } -} - -// NewResource creates a new resource from the options -func (opts Options) NewResource(c newconfig.Config) resource.Resource { - res := resource.Resource{ - GVK: opts.GVK(), - Controller: opts.DoController, - } - +// UpdateResource updates the provided resource with the options +func (opts Options) UpdateResource(res *resource.Resource, c config.Config) { if opts.Plural != "" { res.Plural = opts.Plural - } else { - // If not provided, compute a plural for Kind - res.Plural = resource.RegularPlural(opts.Kind) } if opts.DoAPI { - res.Path = resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()) + res.Path = resource.APIPackagePath(c.GetRepository(), res.Group, res.Version, c.IsMultiGroup()) res.API = &resource.API{ CRDVersion: opts.CRDVersion, Namespaced: opts.Namespaced, } - } else { - // Make sure that the pointer is not nil to prevent pointer dereference errors - res.API = &resource.API{} + } + + if opts.DoController { + res.Controller = true } if opts.DoDefaulting || opts.DoValidation || opts.DoConversion { - res.Path = resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()) - res.Webhooks = &resource.Webhooks{ - WebhookVersion: opts.WebhookVersion, - Defaulting: opts.DoDefaulting, - Validation: opts.DoValidation, - Conversion: opts.DoConversion, + res.Path = resource.APIPackagePath(c.GetRepository(), res.Group, res.Version, c.IsMultiGroup()) + res.Webhooks.WebhookVersion = opts.WebhookVersion + if opts.DoDefaulting { + res.Webhooks.Defaulting = true + } + if opts.DoValidation { + res.Webhooks.Validation = true + } + if opts.DoConversion { + res.Webhooks.Conversion = true } - } else { - // Make sure that the pointer is not nil to prevent pointer dereference errors - res.Webhooks = &resource.Webhooks{} } // domain and path may need to be changed in case we are referring to a builtin core resource: @@ -178,15 +112,18 @@ func (opts Options) NewResource(c newconfig.Config) resource.Resource { // - In any other case, default to => project resource // TODO: need to support '--resource-pkg-path' flag for specifying resourcePath if !opts.DoAPI { - loadedRes, err := c.GetResource(opts.GVK()) - alreadyHasAPI := err == nil && loadedRes.HasAPI() + var alreadyHasAPI bool + if c.GetVersion().Compare(cfgv2.Version) == 0 { + alreadyHasAPI = c.HasResource(res.GVK) + } else { + loadedRes, err := c.GetResource(res.GVK) + alreadyHasAPI = err == nil && loadedRes.HasAPI() + } if !alreadyHasAPI { - if domain, found := coreGroups[opts.Group]; found { + if domain, found := coreGroups[res.Group]; found { res.Domain = domain - res.Path = path.Join("k8s.io", "api", opts.Group, opts.Version) + res.Path = path.Join("k8s.io", "api", res.Group, res.Version) } } } - - return res } diff --git a/pkg/plugins/golang/options_test.go b/pkg/plugins/golang/options_test.go index 8faa101d975..7df8d828907 100644 --- a/pkg/plugins/golang/options_test.go +++ b/pkg/plugins/golang/options_test.go @@ -24,41 +24,37 @@ import ( . "github.com/onsi/gomega" "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) var _ = Describe("Options", func() { - Context("Validate", func() { - DescribeTable("should succeed for valid options", - func(options Options) { Expect(options.Validate()).To(Succeed()) }, - Entry("full GVK", Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate"}), - Entry("missing domain", Options{Group: "crew", Version: "v1", Kind: "FirstMate"}), - Entry("missing group", Options{Domain: "test.io", Version: "v1", Kind: "FirstMate"}), + Context("UpdateResource", func() { + const ( + group = "crew" + domain = "test.io" + version = "v1" + kind = "FirstMate" ) + var ( + gvk = resource.GVK{ + Group: group, + Domain: domain, + Version: version, + Kind: kind, + } - DescribeTable("should fail for invalid options", - func(options Options) { Expect(options.Validate()).NotTo(Succeed()) }, - Entry("group flag captured another flag", Options{Group: "--version"}), - Entry("version flag captured another flag", Options{Version: "--kind"}), - Entry("kind flag captured another flag", Options{Kind: "--group"}), - Entry("missing group and domain", Options{Version: "v1", Kind: "FirstMate"}), - Entry("missing version", Options{Group: "crew", Domain: "test.io", Kind: "FirstMate"}), - Entry("missing kind", Options{Group: "crew", Domain: "test.io", Version: "v1"}), + cfg config.Config ) - }) - - Context("NewResource", func() { - var cfg config.Config BeforeEach(func() { cfg = cfgv3.New() _ = cfg.SetRepository("test") }) - DescribeTable("should succeed if the Resource is valid", + DescribeTable("should succeed", func(options Options) { - Expect(options.Validate()).To(Succeed()) - for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) @@ -66,86 +62,65 @@ var _ = Describe("Options", func() { Expect(cfg.ClearMultiGroup()).To(Succeed()) } - resource := options.NewResource(cfg) - Expect(resource.Validate()).To(Succeed()) - Expect(resource.Group).To(Equal(options.Group)) - Expect(resource.Domain).To(Equal(options.Domain)) - Expect(resource.Version).To(Equal(options.Version)) - Expect(resource.Kind).To(Equal(options.Kind)) - Expect(resource.API).NotTo(BeNil()) + res := resource.Resource{ + GVK: gvk, + Plural: "firstmates", + API: &resource.API{}, + Webhooks: &resource.Webhooks{}, + } + + options.UpdateResource(&res, cfg) + Expect(res.Validate()).To(Succeed()) + Expect(res.GVK.IsEqualTo(gvk)).To(BeTrue()) + if options.Plural != "" { + Expect(res.Plural).To(Equal(options.Plural)) + } if options.DoAPI || options.DoDefaulting || options.DoValidation || options.DoConversion { if multiGroup { - Expect(resource.Path).To(Equal( - path.Join(cfg.GetRepository(), "apis", options.Group, options.Version))) + Expect(res.Path).To(Equal( + path.Join(cfg.GetRepository(), "apis", gvk.Group, gvk.Version))) } else { - Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) + Expect(res.Path).To(Equal(path.Join(cfg.GetRepository(), "api", gvk.Version))) } } else { // Core-resources have a path despite not having an API/Webhook but they are not tested here - Expect(resource.Path).To(Equal("")) + Expect(res.Path).To(Equal("")) } + Expect(res.API).NotTo(BeNil()) if options.DoAPI { - Expect(resource.API.CRDVersion).To(Equal(options.CRDVersion)) - Expect(resource.API.Namespaced).To(Equal(options.Namespaced)) - Expect(resource.API.IsEmpty()).To(BeFalse()) + Expect(res.API.CRDVersion).To(Equal(options.CRDVersion)) + Expect(res.API.Namespaced).To(Equal(options.Namespaced)) + Expect(res.API.IsEmpty()).To(BeFalse()) } else { - Expect(resource.API.IsEmpty()).To(BeTrue()) + Expect(res.API.IsEmpty()).To(BeTrue()) } - Expect(resource.Controller).To(Equal(options.DoController)) - Expect(resource.Webhooks).NotTo(BeNil()) + Expect(res.Controller).To(Equal(options.DoController)) + Expect(res.Webhooks).NotTo(BeNil()) if options.DoDefaulting || options.DoValidation || options.DoConversion { - Expect(resource.Webhooks.WebhookVersion).To(Equal(options.WebhookVersion)) - Expect(resource.Webhooks.Defaulting).To(Equal(options.DoDefaulting)) - Expect(resource.Webhooks.Validation).To(Equal(options.DoValidation)) - Expect(resource.Webhooks.Conversion).To(Equal(options.DoConversion)) - Expect(resource.Webhooks.IsEmpty()).To(BeFalse()) + Expect(res.Webhooks.WebhookVersion).To(Equal(options.WebhookVersion)) + Expect(res.Webhooks.Defaulting).To(Equal(options.DoDefaulting)) + Expect(res.Webhooks.Validation).To(Equal(options.DoValidation)) + Expect(res.Webhooks.Conversion).To(Equal(options.DoConversion)) + Expect(res.Webhooks.IsEmpty()).To(BeFalse()) } else { - Expect(resource.Webhooks.IsEmpty()).To(BeTrue()) + Expect(res.Webhooks.IsEmpty()).To(BeTrue()) } - Expect(resource.QualifiedGroup()).To(Equal(options.Group + "." + options.Domain)) - Expect(resource.PackageName()).To(Equal(options.Group)) - Expect(resource.ImportAlias()).To(Equal(options.Group + options.Version)) + Expect(res.QualifiedGroup()).To(Equal(gvk.Group + "." + gvk.Domain)) + Expect(res.PackageName()).To(Equal(gvk.Group)) + Expect(res.ImportAlias()).To(Equal(gvk.Group + gvk.Version)) } }, - Entry("basic", Options{ - Group: "crew", - Domain: "test.io", - Version: "v1", - Kind: "FirstMate", - }), - Entry("API", Options{ - Group: "crew", - Domain: "test.io", - Version: "v1", - Kind: "FirstMate", - DoAPI: true, - CRDVersion: "v1", - Namespaced: true, - }), - Entry("Controller", Options{ - Group: "crew", - Domain: "test.io", - Version: "v1", - Kind: "FirstMate", - DoController: true, - }), - Entry("Webhooks", Options{ - Group: "crew", - Domain: "test.io", - Version: "v1", - Kind: "FirstMate", - WebhookVersion: "v1", - DoDefaulting: true, - DoValidation: true, - DoConversion: true, - }), + Entry("when updating nothing", Options{}), + Entry("when updating the plural", Options{Plural: "mates"}), + Entry("when updating the API", Options{DoAPI: true, CRDVersion: "v1", Namespaced: true}), + Entry("when updating the Controller", Options{DoController: true}), + Entry("when updating Webhooks", + Options{WebhookVersion: "v1", DoDefaulting: true, DoValidation: true, DoConversion: true}), ) - DescribeTable("should default the Plural by pluralizing the Kind", - func(kind, plural string) { - options := Options{Group: "crew", Version: "v1", Kind: kind} - Expect(options.Validate()).To(Succeed()) - + DescribeTable("should use core apis", + func(group, qualified string) { + options := Options{} for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) @@ -153,48 +128,39 @@ var _ = Describe("Options", func() { Expect(cfg.ClearMultiGroup()).To(Succeed()) } - resource := options.NewResource(cfg) - Expect(resource.Validate()).To(Succeed()) - Expect(resource.Plural).To(Equal(plural)) - } - }, - Entry("for `FirstMate`", "FirstMate", "firstmates"), - Entry("for `Fish`", "Fish", "fish"), - Entry("for `Helmswoman`", "Helmswoman", "helmswomen"), - ) - - DescribeTable("should keep the Plural if specified", - func(kind, plural string) { - options := Options{Group: "crew", Version: "v1", Kind: kind, Plural: plural} - Expect(options.Validate()).To(Succeed()) - - for _, multiGroup := range []bool{false, true} { - if multiGroup { - Expect(cfg.SetMultiGroup()).To(Succeed()) - } else { - Expect(cfg.ClearMultiGroup()).To(Succeed()) + res := resource.Resource{ + GVK: resource.GVK{ + Group: group, + Domain: domain, + Version: version, + Kind: kind, + }, + Plural: "firstmates", + API: &resource.API{}, + Webhooks: &resource.Webhooks{}, } - resource := options.NewResource(cfg) - Expect(resource.Validate()).To(Succeed()) - Expect(resource.Plural).To(Equal(plural)) + options.UpdateResource(&res, cfg) + Expect(res.Validate()).To(Succeed()) + + Expect(res.Path).To(Equal(path.Join("k8s.io", "api", group, version))) + Expect(res.HasAPI()).To(BeFalse()) + Expect(res.QualifiedGroup()).To(Equal(qualified)) } }, - Entry("for `FirstMate`", "FirstMate", "mates"), - Entry("for `Fish`", "Fish", "shoal"), + Entry("for `apps`", "apps", "apps"), + Entry("for `authentication`", "authentication", "authentication.k8s.io"), ) - DescribeTable("should allow hyphens and dots in group names", - func(group, safeGroup string) { - options := Options{ - Group: group, - Domain: "test.io", - Version: "v1", - Kind: "FirstMate", - DoAPI: true, // Scaffold the API so that the path is saved - } - Expect(options.Validate()).To(Succeed()) + DescribeTable("should use core apis with project version 2", + // This needs a separate test because project version 2 didn't store API and therefore + // the `HasAPI` method of the resource obtained with `GetResource` will always return false. + // Instead, the existence of a resource in the list means the API was scaffolded. + func(group, qualified string) { + cfg = cfgv2.New() + _ = cfg.SetRepository("test") + options := Options{} for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) @@ -202,100 +168,28 @@ var _ = Describe("Options", func() { Expect(cfg.ClearMultiGroup()).To(Succeed()) } - resource := options.NewResource(cfg) - Expect(resource.Validate()).To(Succeed()) - Expect(resource.Group).To(Equal(options.Group)) - if multiGroup { - Expect(resource.Path).To(Equal( - path.Join(cfg.GetRepository(), "apis", options.Group, options.Version))) - } else { - Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) + res := resource.Resource{ + GVK: resource.GVK{ + Group: group, + Domain: domain, + Version: version, + Kind: kind, + }, + Plural: "firstmates", + API: &resource.API{}, + Webhooks: &resource.Webhooks{}, } - Expect(resource.QualifiedGroup()).To(Equal(options.Group + "." + options.Domain)) - Expect(resource.PackageName()).To(Equal(safeGroup)) - Expect(resource.ImportAlias()).To(Equal(safeGroup + options.Version)) - } - }, - Entry("for hyphen-containing group", "my-project", "myproject"), - Entry("for dot-containing group", "my.project", "myproject"), - ) - It("should not append '.' if provided an empty domain", func() { - options := Options{Group: "crew", Version: "v1", Kind: "FirstMate"} - Expect(options.Validate()).To(Succeed()) - - for _, multiGroup := range []bool{false, true} { - if multiGroup { - Expect(cfg.SetMultiGroup()).To(Succeed()) - } else { - Expect(cfg.ClearMultiGroup()).To(Succeed()) - } - - resource := options.NewResource(cfg) - Expect(resource.Validate()).To(Succeed()) - Expect(resource.QualifiedGroup()).To(Equal(options.Group)) - } - }) + options.UpdateResource(&res, cfg) + Expect(res.Validate()).To(Succeed()) - DescribeTable("should use core apis", - func(group, qualified string) { - options := Options{ - Group: group, - Domain: "test.io", - Version: "v1", - Kind: "FirstMate", - } - Expect(options.Validate()).To(Succeed()) - - for _, multiGroup := range []bool{false, true} { - if multiGroup { - Expect(cfg.SetMultiGroup()).To(Succeed()) - } else { - Expect(cfg.ClearMultiGroup()).To(Succeed()) - } - - resource := options.NewResource(cfg) - Expect(resource.Validate()).To(Succeed()) - Expect(resource.Path).To(Equal(path.Join("k8s.io", "api", options.Group, options.Version))) - Expect(resource.API).NotTo(BeNil()) - Expect(resource.API.IsEmpty()).To(BeTrue()) - Expect(resource.QualifiedGroup()).To(Equal(qualified)) + Expect(res.Path).To(Equal(path.Join("k8s.io", "api", group, version))) + Expect(res.HasAPI()).To(BeFalse()) + Expect(res.QualifiedGroup()).To(Equal(qualified)) } }, Entry("for `apps`", "apps", "apps"), Entry("for `authentication`", "authentication", "authentication.k8s.io"), ) - - It("should use domain if the group is empty", func() { - safeDomain := "testio" - - options := Options{ - Domain: "test.io", - Version: "v1", - Kind: "FirstMate", - DoAPI: true, // Scaffold the API so that the path is saved - } - Expect(options.Validate()).To(Succeed()) - - for _, multiGroup := range []bool{false, true} { - if multiGroup { - Expect(cfg.SetMultiGroup()).To(Succeed()) - } else { - Expect(cfg.ClearMultiGroup()).To(Succeed()) - } - - resource := options.NewResource(cfg) - Expect(resource.Validate()).To(Succeed()) - Expect(resource.Group).To(Equal("")) - if multiGroup { - Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "apis", options.Version))) - } else { - Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) - } - Expect(resource.QualifiedGroup()).To(Equal(options.Domain)) - Expect(resource.PackageName()).To(Equal(safeDomain)) - Expect(resource.ImportAlias()).To(Equal(safeDomain + options.Version)) - } - }) }) }) diff --git a/pkg/plugins/golang/v2/api.go b/pkg/plugins/golang/v2/api.go index bdca3497512..336a0626691 100644 --- a/pkg/plugins/golang/v2/api.go +++ b/pkg/plugins/golang/v2/api.go @@ -20,32 +20,27 @@ import ( "bufio" "errors" "fmt" - "io/ioutil" "os" - "path/filepath" - "strings" + "github.com/spf13/afero" "github.com/spf13/pflag" "sigs.k8s.io/kubebuilder/v3/pkg/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery/util" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/util" - "sigs.k8s.io/kubebuilder/v3/plugins/addon" ) +var _ plugin.CreateAPISubcommand = &createAPISubcommand{} + type createAPISubcommand struct { config config.Config - // pattern indicates that we should use a plugin to build according to a pattern - pattern string - - options *Options + options *goPlugin.Options - resource resource.Resource + resource *resource.Resource // Check if we have to scaffold resource and/or controller resourceFlag *pflag.Flag @@ -58,13 +53,8 @@ type createAPISubcommand struct { runMake bool } -var ( - _ plugin.CreateAPISubcommand = &createAPISubcommand{} - _ cmdutil.RunOptions = &createAPISubcommand{} -) - -func (p createAPISubcommand) UpdateContext(ctx *plugin.Context) { - ctx.Description = `Scaffold a Kubernetes API by creating a Resource definition and / or a Controller. +func (p *createAPISubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + subcmdMeta.Description = `Scaffold a Kubernetes API by creating a Resource definition and / or a Controller. create resource will prompt the user for if it should scaffold the Resource and / or Controller. To only scaffold a Controller for an existing Resource, select "n" for Resource. To only define @@ -72,7 +62,7 @@ the schema for a Resource without writing a Controller, select "n" for Controlle After the scaffold is written, api will run make on the project. ` - ctx.Examples = fmt.Sprintf(` # Create a frigates API with Group: ship, Version: v1beta1 and Kind: Frigate + subcmdMeta.Examples = fmt.Sprintf(` # Create a frigates API with Group: ship, Version: v1beta1 and Kind: Frigate %s create api --group ship --version v1beta1 --kind Frigate # Edit the API Scheme @@ -89,32 +79,20 @@ After the scaffold is written, api will run make on the project. # Regenerate code and run against the Kubernetes cluster configured by ~/.kube/config make run - `, - ctx.CommandName) + `, cliMeta.CommandName) } func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { fs.BoolVar(&p.runMake, "make", true, "if true, run `make generate` after generating files") - if os.Getenv("KUBEBUILDER_ENABLE_PLUGINS") != "" { - fs.StringVar(&p.pattern, "pattern", "", - "generates an API following an extension pattern (addon)") - } - fs.BoolVar(&p.force, "force", false, "attempt to create resource even if it already exists") - p.options = &Options{} - fs.StringVar(&p.options.Group, "group", "", "resource Group") - p.options.Domain = p.config.GetDomain() - fs.StringVar(&p.options.Version, "version", "", "resource Version") - fs.StringVar(&p.options.Kind, "kind", "", "resource Kind") - // p.options.Plural can be set to specify an irregular plural form + p.options = &goPlugin.Options{CRDVersion: "v1beta1"} fs.BoolVar(&p.options.DoAPI, "resource", true, "if set, generate the resource without prompting the user") p.resourceFlag = fs.Lookup("resource") - p.options.CRDVersion = "v1beta1" fs.BoolVar(&p.options.Namespaced, "namespaced", true, "resource is namespaced") fs.BoolVar(&p.options.DoController, "controller", true, @@ -122,11 +100,19 @@ func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { p.controllerFlag = fs.Lookup("controller") } -func (p *createAPISubcommand) InjectConfig(c config.Config) { +func (p *createAPISubcommand) InjectConfig(c config.Config) error { p.config = c + + return nil } -func (p *createAPISubcommand) Run() error { +func (p *createAPISubcommand) InjectResource(res *resource.Resource) error { + p.resource = res + + if p.resource.Group == "" { + return fmt.Errorf("group cannot be empty") + } + // Ask for API and Controller if not specified reader := bufio.NewReader(os.Stdin) if !p.resourceFlag.Changed { @@ -138,16 +124,7 @@ func (p *createAPISubcommand) Run() error { p.options.DoController = util.YesNo(reader) } - // Create the resource from the options - p.resource = p.options.NewResource(p.config) - - return cmdutil.Run(p) -} - -func (p *createAPISubcommand) Validate() error { - if err := p.options.Validate(); err != nil { - return err - } + p.options.UpdateResource(p.resource, p.config) if err := p.resource.Validate(); err != nil { return err @@ -170,45 +147,16 @@ func (p *createAPISubcommand) Validate() error { return nil } -func (p *createAPISubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { - // Load the boilerplate - bp, err := ioutil.ReadFile(filepath.Join("hack", "boilerplate.go.txt")) // nolint:gosec - if err != nil { - return nil, fmt.Errorf("unable to load boilerplate: %v", err) - } - - // Load the requested plugins - plugins := make([]model.Plugin, 0) - switch strings.ToLower(p.pattern) { - case "": - // Default pattern - case "addon": - plugins = append(plugins, &addon.Plugin{}) - default: - return nil, fmt.Errorf("unknown pattern %q", p.pattern) - } - - return scaffolds.NewAPIScaffolder(p.config, string(bp), p.resource, p.force, plugins), nil +func (p *createAPISubcommand) Scaffold(fs afero.Fs) error { + scaffolder := scaffolds.NewAPIScaffolder(p.config, *p.resource, p.force) + scaffolder.InjectFS(fs) + return scaffolder.Scaffold() } func (p *createAPISubcommand) PostScaffold() error { - // Load the requested plugins - switch strings.ToLower(p.pattern) { - case "": - // Default pattern - case "addon": - // Ensure that we are pinning sigs.k8s.io/kubebuilder-declarative-pattern version - err := util.RunCmd("Get controller runtime", "go", "get", - "sigs.k8s.io/kubebuilder-declarative-pattern@"+scaffolds.KbDeclarativePattern) - if err != nil { - return err - } - default: - return fmt.Errorf("unknown pattern %q", p.pattern) - } - - if p.runMake { // TODO: check if API was scaffolded + if p.runMake && p.resource.HasAPI() { return util.RunCmd("Running make", "make", "generate") } + return nil } diff --git a/pkg/plugins/golang/v2/edit.go b/pkg/plugins/golang/v2/edit.go index 7c232a44bec..988588082bc 100644 --- a/pkg/plugins/golang/v2/edit.go +++ b/pkg/plugins/golang/v2/edit.go @@ -19,56 +19,47 @@ package v2 import ( "fmt" + "github.com/spf13/afero" "github.com/spf13/pflag" "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" ) +var _ plugin.EditSubcommand = &editSubcommand{} + type editSubcommand struct { config config.Config multigroup bool } -var ( - _ plugin.EditSubcommand = &editSubcommand{} - _ cmdutil.RunOptions = &editSubcommand{} -) - -func (p *editSubcommand) UpdateContext(ctx *plugin.Context) { - ctx.Description = `This command will edit the project configuration. You can have single or multi group project.` - - ctx.Examples = fmt.Sprintf(`# Enable the multigroup layout - %s edit --multigroup +func (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + subcmdMeta.Description = `This command will edit the project configuration. +Features supported: + - Toggle between single or multi group projects. +` + subcmdMeta.Examples = fmt.Sprintf(`# Enable the multigroup layout + %[1]s edit --multigroup # Disable the multigroup layout - %s edit --multigroup=false - `, ctx.CommandName, ctx.CommandName) + %[1]s edit --multigroup=false + `, cliMeta.CommandName) } func (p *editSubcommand) BindFlags(fs *pflag.FlagSet) { fs.BoolVar(&p.multigroup, "multigroup", false, "enable or disable multigroup layout") } -func (p *editSubcommand) InjectConfig(c config.Config) { +func (p *editSubcommand) InjectConfig(c config.Config) error { p.config = c -} -func (p *editSubcommand) Run() error { - return cmdutil.Run(p) -} - -func (p *editSubcommand) Validate() error { return nil } -func (p *editSubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { - return scaffolds.NewEditScaffolder(p.config, p.multigroup), nil -} - -func (p *editSubcommand) PostScaffold() error { - return nil +func (p *editSubcommand) Scaffold(fs afero.Fs) error { + scaffolder := scaffolds.NewEditScaffolder(p.config, p.multigroup) + scaffolder.InjectFS(fs) + return scaffolder.Scaffold() } diff --git a/pkg/plugins/golang/v2/init.go b/pkg/plugins/golang/v2/init.go index af767e74c65..63f0558d096 100644 --- a/pkg/plugins/golang/v2/init.go +++ b/pkg/plugins/golang/v2/init.go @@ -22,17 +22,19 @@ import ( "path/filepath" "strings" + "github.com/spf13/afero" "github.com/spf13/pflag" "sigs.k8s.io/kubebuilder/v3/pkg/config" cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery/util" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/util" ) +var _ plugin.InitSubcommand = &initSubcommand{} + type initSubcommand struct { config config.Config @@ -53,30 +55,24 @@ type initSubcommand struct { skipGoVersionCheck bool } -var ( - _ plugin.InitSubcommand = &initSubcommand{} - _ cmdutil.RunOptions = &initSubcommand{} -) +func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + p.commandName = cliMeta.CommandName -func (p *initSubcommand) UpdateContext(ctx *plugin.Context) { - ctx.Description = `Initialize a new project including vendor/ directory and Go package directories. + subcmdMeta.Description = `Initialize a new project including vendor/ directory and Go package directories. Writes the following files: - a boilerplate license file - a PROJECT file with the domain and repo - a Makefile to build the project - a go.mod with project dependencies -- a Kustomization.yaml for customizating manifests +- a Kustomization.yaml for customizing manifests - a Patch file for customizing image for manager manifests - a Patch file for enabling prometheus metrics - a main.go to run ` - ctx.Examples = fmt.Sprintf(` # Scaffold a project using the apache2 license with "The Kubernetes authors" as owners - %s init --project-version=2 --domain example.org --license apache2 --owner "The Kubernetes authors" -`, - ctx.CommandName) - - p.commandName = ctx.CommandName + subcmdMeta.Examples = fmt.Sprintf(`# Scaffold a project using the apache2 license with %[2]q as owners + %[1]s init --project-version=2 --domain example.org --license apache2 --owner %[2]q +`, cliMeta.CommandName, "The Kubernetes authors") } func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) { @@ -95,30 +91,26 @@ func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) { fs.StringVar(&p.domain, "domain", "my.domain", "domain for groups") fs.StringVar(&p.repo, "repo", "", "name to use for go module (e.g., github.com/user/repo), "+ "defaults to the go package of the current working directory.") - if p.config.GetVersion().Compare(cfgv2.Version) > 0 { - fs.StringVar(&p.name, "project-name", "", "name of this project") - } + fs.StringVar(&p.name, "project-name", "", "name of this project") } -func (p *initSubcommand) InjectConfig(c config.Config) { - // v2+ project configs get a 'layout' value. - if c.GetVersion().Compare(cfgv2.Version) > 0 { - _ = c.SetLayout(plugin.KeyFor(Plugin{})) - } - +func (p *initSubcommand) InjectConfig(c config.Config) error { p.config = c -} -func (p *initSubcommand) Run() error { - return cmdutil.Run(p) -} + if err := p.config.SetDomain(p.domain); err != nil { + return err + } -func (p *initSubcommand) Validate() error { - // Requires go1.11+ - if !p.skipGoVersionCheck { - if err := util.ValidateGoVersion(); err != nil { - return err + // Try to guess repository if flag is not set. + if p.repo == "" { + repoPath, err := util.FindCurrentRepo() + if err != nil { + return fmt.Errorf("error finding current repository: %v", err) } + p.repo = repoPath + } + if err := p.config.SetRepository(p.repo); err != nil { + return err } if p.config.GetVersion().Compare(cfgv2.Version) > 0 { @@ -134,34 +126,29 @@ func (p *initSubcommand) Validate() error { if err := validation.IsDNS1123Label(p.name); err != nil { return fmt.Errorf("project name (%s) is invalid: %v", p.name, err) } - } - - // Try to guess repository if flag is not set. - if p.repo == "" { - repoPath, err := util.FindCurrentRepo() - if err != nil { - return fmt.Errorf("error finding current repository: %v", err) + if err := p.config.SetProjectName(p.name); err != nil { + return err } - p.repo = repoPath } return nil } -func (p *initSubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { - if err := p.config.SetDomain(p.domain); err != nil { - return nil, err - } - if err := p.config.SetRepository(p.repo); err != nil { - return nil, err - } - if p.config.GetVersion().Compare(cfgv2.Version) > 0 { - if err := p.config.SetProjectName(p.name); err != nil { - return nil, err +func (p *initSubcommand) PreScaffold(afero.Fs) error { + // Requires go1.11+ + if !p.skipGoVersionCheck { + if err := util.ValidateGoVersion(); err != nil { + return err } } - return scaffolds.NewInitScaffolder(p.config, p.license, p.owner), nil + return nil +} + +func (p *initSubcommand) Scaffold(fs afero.Fs) error { + scaffolder := scaffolds.NewInitScaffolder(p.config, p.license, p.owner) + scaffolder.InjectFS(fs) + return scaffolder.Scaffold() } func (p *initSubcommand) PostScaffold() error { diff --git a/pkg/plugins/golang/v2/options.go b/pkg/plugins/golang/v2/options.go deleted file mode 100644 index 52f4410b838..00000000000 --- a/pkg/plugins/golang/v2/options.go +++ /dev/null @@ -1,198 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v2 - -import ( - "fmt" - "path" - "strings" - - newconfig "sigs.k8s.io/kubebuilder/v3/pkg/config" - cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" - "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" -) - -const ( - groupPresent = "group flag present but empty" - versionPresent = "version flag present but empty" - kindPresent = "kind flag present but empty" - groupRequired = "group cannot be empty" - versionRequired = "version cannot be empty" - kindRequired = "kind cannot be empty" -) - -var ( - coreGroups = map[string]string{ - "admission": "k8s.io", - "admissionregistration": "k8s.io", - "apps": "", - "auditregistration": "k8s.io", - "apiextensions": "k8s.io", - "authentication": "k8s.io", - "authorization": "k8s.io", - "autoscaling": "", - "batch": "", - "certificates": "k8s.io", - "coordination": "k8s.io", - "core": "", - "events": "k8s.io", - "extensions": "", - "imagepolicy": "k8s.io", - "networking": "k8s.io", - "node": "k8s.io", - "metrics": "k8s.io", - "policy": "", - "rbac.authorization": "k8s.io", - "scheduling": "k8s.io", - "setting": "k8s.io", - "storage": "k8s.io", - } -) - -// Options contains the information required to build a new resource.Resource. -type Options struct { - // Group is the resource's group. Does not contain the domain. - Group string - // Domain is the resource's domain. - Domain string - // Version is the resource's version. - Version string - // Kind is the resource's kind. - Kind string - - // Plural is the resource's kind plural form. - // Optional - Plural string - - // CRDVersion is the CustomResourceDefinition API version that will be used for the resource. - CRDVersion string - // WebhookVersion is the {Validating,Mutating}WebhookConfiguration API version that will be used for the resource. - WebhookVersion string - - // Namespaced is true if the resource should be namespaced. - Namespaced bool - - // Flags that define which parts should be scaffolded - DoAPI bool - DoController bool - DoDefaulting bool - DoValidation bool - DoConversion bool -} - -// Validate verifies that all the fields have valid values -func (opts Options) Validate() error { - // Check that the required flags did not get a flag as their value - // We can safely look for a '-' as the first char as none of the fields accepts it - // NOTE: We must do this for all the required flags first or we may output the wrong - // error as flags may seem to be missing because Cobra assigned them to another flag. - if strings.HasPrefix(opts.Group, "-") { - return fmt.Errorf(groupPresent) - } - if strings.HasPrefix(opts.Version, "-") { - return fmt.Errorf(versionPresent) - } - if strings.HasPrefix(opts.Kind, "-") { - return fmt.Errorf(kindPresent) - } - - // Now we can check that all the required flags are not empty - if len(opts.Group) == 0 { - return fmt.Errorf(groupRequired) - } - if len(opts.Version) == 0 { - return fmt.Errorf(versionRequired) - } - if len(opts.Kind) == 0 { - return fmt.Errorf(kindRequired) - } - - return nil -} - -// GVK returns the GVK identifier of a resource. -func (opts Options) GVK() resource.GVK { - return resource.GVK{ - Group: opts.Group, - Domain: opts.Domain, - Version: opts.Version, - Kind: opts.Kind, - } -} - -// NewResource creates a new resource from the options -func (opts Options) NewResource(c newconfig.Config) resource.Resource { - res := resource.Resource{ - GVK: opts.GVK(), - Controller: opts.DoController, - } - - if opts.Plural != "" { - res.Plural = opts.Plural - } else { - // If not provided, compute a plural for for Kind - res.Plural = resource.RegularPlural(opts.Kind) - } - - if opts.DoAPI { - res.Path = resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()) - res.API = &resource.API{ - CRDVersion: opts.CRDVersion, - Namespaced: opts.Namespaced, - } - } else { - // Make sure that the pointer is not nil to prevent pointer dereference errors - res.API = &resource.API{} - } - - if opts.DoDefaulting || opts.DoValidation || opts.DoConversion { - res.Path = resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()) - res.Webhooks = &resource.Webhooks{ - WebhookVersion: opts.WebhookVersion, - Defaulting: opts.DoDefaulting, - Validation: opts.DoValidation, - Conversion: opts.DoConversion, - } - } else { - // Make sure that the pointer is not nil to prevent pointer dereference errors - res.Webhooks = &resource.Webhooks{} - } - - // domain and path may need to be changed in case we are referring to a builtin core resource: - // - Check if we are scaffolding the resource now => project resource - // - Check if we already scaffolded the resource => project resource - // - Check if the resource group is a well-known core group => builtin core resource - // - In any other case, default to => project resource - // TODO: need to support '--resource-pkg-path' flag for specifying resourcePath - if !opts.DoAPI { - var alreadyHasAPI bool - if c.GetVersion().Compare(cfgv2.Version) == 0 { - alreadyHasAPI = c.HasResource(opts.GVK()) - } else { - loadedRes, err := c.GetResource(opts.GVK()) - alreadyHasAPI = err == nil && loadedRes.HasAPI() - } - if !alreadyHasAPI { - if domain, found := coreGroups[opts.Group]; found { - res.Domain = domain - res.Path = path.Join("k8s.io", "api", opts.Group, opts.Version) - } - } - } - - return res -} diff --git a/pkg/plugins/golang/v2/options_test.go b/pkg/plugins/golang/v2/options_test.go deleted file mode 100644 index 692a262e5ca..00000000000 --- a/pkg/plugins/golang/v2/options_test.go +++ /dev/null @@ -1,268 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v2 - -import ( - "path" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - - "sigs.k8s.io/kubebuilder/v3/pkg/config" - cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" -) - -var _ = Describe("Options", func() { - Context("Validate", func() { - DescribeTable("should succeed for valid options", - func(options Options) { Expect(options.Validate()).To(Succeed()) }, - Entry("full GVK", Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate"}), - Entry("missing domain", Options{Group: "crew", Version: "v1", Kind: "FirstMate"}), - ) - - DescribeTable("should fail for invalid options", - func(options Options) { Expect(options.Validate()).NotTo(Succeed()) }, - Entry("group flag captured another flag", Options{Group: "--version"}), - Entry("version flag captured another flag", Options{Version: "--kind"}), - Entry("kind flag captured another flag", Options{Kind: "--group"}), - Entry("missing group", Options{Domain: "test.io", Version: "v1", Kind: "FirstMate"}), - Entry("missing version", Options{Group: "crew", Domain: "test.io", Kind: "FirstMate"}), - Entry("missing kind", Options{Group: "crew", Domain: "test.io", Version: "v1"}), - ) - }) - - Context("NewResource", func() { - var cfg config.Config - - BeforeEach(func() { - cfg = cfgv2.New() - _ = cfg.SetRepository("test") - }) - - DescribeTable("should succeed if the Resource is valid", - func(options Options) { - Expect(options.Validate()).To(Succeed()) - - for _, multiGroup := range []bool{false, true} { - if multiGroup { - Expect(cfg.SetMultiGroup()).To(Succeed()) - } else { - Expect(cfg.ClearMultiGroup()).To(Succeed()) - } - - resource := options.NewResource(cfg) - Expect(resource.Validate()).To(Succeed()) - Expect(resource.Group).To(Equal(options.Group)) - Expect(resource.Domain).To(Equal(options.Domain)) - Expect(resource.Version).To(Equal(options.Version)) - Expect(resource.Kind).To(Equal(options.Kind)) - Expect(resource.API).NotTo(BeNil()) - if options.DoAPI || options.DoDefaulting || options.DoValidation || options.DoConversion { - if multiGroup { - Expect(resource.Path).To(Equal( - path.Join(cfg.GetRepository(), "apis", options.Group, options.Version))) - } else { - Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) - } - } else { - // Core-resources have a path despite not having an API/Webhook but they are not tested here - Expect(resource.Path).To(Equal("")) - } - if options.DoAPI { - Expect(resource.API.CRDVersion).To(Equal(options.CRDVersion)) - Expect(resource.API.Namespaced).To(Equal(options.Namespaced)) - Expect(resource.API.IsEmpty()).To(BeFalse()) - } else { - Expect(resource.API.IsEmpty()).To(BeTrue()) - } - Expect(resource.Controller).To(Equal(options.DoController)) - Expect(resource.Webhooks).NotTo(BeNil()) - if options.DoDefaulting || options.DoValidation || options.DoConversion { - Expect(resource.Webhooks.WebhookVersion).To(Equal(options.WebhookVersion)) - Expect(resource.Webhooks.Defaulting).To(Equal(options.DoDefaulting)) - Expect(resource.Webhooks.Validation).To(Equal(options.DoValidation)) - Expect(resource.Webhooks.Conversion).To(Equal(options.DoConversion)) - Expect(resource.Webhooks.IsEmpty()).To(BeFalse()) - } else { - Expect(resource.Webhooks.IsEmpty()).To(BeTrue()) - } - Expect(resource.QualifiedGroup()).To(Equal(options.Group + "." + options.Domain)) - Expect(resource.PackageName()).To(Equal(options.Group)) - Expect(resource.ImportAlias()).To(Equal(options.Group + options.Version)) - } - }, - Entry("basic", Options{ - Group: "crew", - Domain: "test.io", - Version: "v1", - Kind: "FirstMate", - }), - Entry("API", Options{ - Group: "crew", - Domain: "test.io", - Version: "v1", - Kind: "FirstMate", - DoAPI: true, - CRDVersion: "v1beta1", - Namespaced: true, - }), - Entry("Controller", Options{ - Group: "crew", - Domain: "test.io", - Version: "v1", - Kind: "FirstMate", - DoController: true, - }), - Entry("Webhooks", Options{ - Group: "crew", - Domain: "test.io", - Version: "v1", - Kind: "FirstMate", - DoDefaulting: true, - DoValidation: true, - DoConversion: true, - WebhookVersion: "v1beta1", - }), - ) - - DescribeTable("should default the Plural by pluralizing the Kind", - func(kind, plural string) { - options := Options{Group: "crew", Version: "v1", Kind: kind} - Expect(options.Validate()).To(Succeed()) - - for _, multiGroup := range []bool{false, true} { - if multiGroup { - Expect(cfg.SetMultiGroup()).To(Succeed()) - } else { - Expect(cfg.ClearMultiGroup()).To(Succeed()) - } - - resource := options.NewResource(cfg) - Expect(resource.Validate()).To(Succeed()) - Expect(resource.Plural).To(Equal(plural)) - } - }, - Entry("for `FirstMate`", "FirstMate", "firstmates"), - Entry("for `Fish`", "Fish", "fish"), - Entry("for `Helmswoman`", "Helmswoman", "helmswomen"), - ) - - DescribeTable("should keep the Plural if specified", - func(kind, plural string) { - options := Options{Group: "crew", Version: "v1", Kind: kind, Plural: plural} - Expect(options.Validate()).To(Succeed()) - - for _, multiGroup := range []bool{false, true} { - if multiGroup { - Expect(cfg.SetMultiGroup()).To(Succeed()) - } else { - Expect(cfg.ClearMultiGroup()).To(Succeed()) - } - - resource := options.NewResource(cfg) - Expect(resource.Validate()).To(Succeed()) - Expect(resource.Plural).To(Equal(plural)) - } - }, - Entry("for `FirstMate`", "FirstMate", "mates"), - Entry("for `Fish`", "Fish", "shoal"), - ) - - DescribeTable("should allow hyphens and dots in group names", - func(group, safeGroup string) { - options := Options{ - Group: group, - Domain: "test.io", - Version: "v1", - Kind: "FirstMate", - DoAPI: true, // Scaffold the API so that the path is saved - } - Expect(options.Validate()).To(Succeed()) - - for _, multiGroup := range []bool{false, true} { - if multiGroup { - Expect(cfg.SetMultiGroup()).To(Succeed()) - } else { - Expect(cfg.ClearMultiGroup()).To(Succeed()) - } - - resource := options.NewResource(cfg) - Expect(resource.Validate()).To(Succeed()) - Expect(resource.Group).To(Equal(options.Group)) - if multiGroup { - Expect(resource.Path).To(Equal( - path.Join(cfg.GetRepository(), "apis", options.Group, options.Version))) - } else { - Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) - } - Expect(resource.QualifiedGroup()).To(Equal(options.Group + "." + options.Domain)) - Expect(resource.PackageName()).To(Equal(safeGroup)) - Expect(resource.ImportAlias()).To(Equal(safeGroup + options.Version)) - } - }, - Entry("for hyphen-containing group", "my-project", "myproject"), - Entry("for dot-containing group", "my.project", "myproject"), - ) - - It("should not append '.' if provided an empty domain", func() { - options := Options{Group: "crew", Version: "v1", Kind: "FirstMate"} - Expect(options.Validate()).To(Succeed()) - - for _, multiGroup := range []bool{false, true} { - if multiGroup { - Expect(cfg.SetMultiGroup()).To(Succeed()) - } else { - Expect(cfg.ClearMultiGroup()).To(Succeed()) - } - - resource := options.NewResource(cfg) - Expect(resource.Validate()).To(Succeed()) - Expect(resource.QualifiedGroup()).To(Equal(options.Group)) - } - }) - - DescribeTable("should use core apis", - func(group, qualified string) { - options := Options{ - Group: group, - Domain: "test.io", - Version: "v1", - Kind: "FirstMate", - } - Expect(options.Validate()).To(Succeed()) - - for _, multiGroup := range []bool{false, true} { - if multiGroup { - Expect(cfg.SetMultiGroup()).To(Succeed()) - } else { - Expect(cfg.ClearMultiGroup()).To(Succeed()) - } - - resource := options.NewResource(cfg) - Expect(resource.Validate()).To(Succeed()) - Expect(resource.Path).To(Equal(path.Join("k8s.io", "api", options.Group, options.Version))) - Expect(resource.API).NotTo(BeNil()) - Expect(resource.API.IsEmpty()).To(BeTrue()) - Expect(resource.QualifiedGroup()).To(Equal(qualified)) - } - }, - Entry("for `apps`", "apps", "apps"), - Entry("for `authentication`", "authentication", "authentication.k8s.io"), - ) - }) -}) diff --git a/pkg/plugins/golang/v2/scaffolds/api.go b/pkg/plugins/golang/v2/scaffolds/api.go index 353aac0e5f0..4ddaf7f5fc8 100644 --- a/pkg/plugins/golang/v2/scaffolds/api.go +++ b/pkg/plugins/golang/v2/scaffolds/api.go @@ -19,10 +19,13 @@ package scaffolds import ( "fmt" + "github.com/spf13/afero" + "sigs.k8s.io/kubebuilder/v3/pkg/config" cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" - "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/api" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd" @@ -30,25 +33,19 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/hack" ) -// KbDeclarativePattern is the sigs.k8s.io/kubebuilder-declarative-pattern version -// (used only to gen api with --pattern=addon) -const KbDeclarativePattern = "v0.0.0-20200522144838-848d48e5b073" - -var _ cmdutil.Scaffolder = &apiScaffolder{} +var _ plugins.Scaffolder = &apiScaffolder{} // apiScaffolder contains configuration for generating scaffolding for Go type // representing the API and controller that implements the behavior for the API. type apiScaffolder struct { - config config.Config - boilerplate string - resource resource.Resource + config config.Config + resource resource.Resource - // plugins is the list of plugins we should allow to transform our generated scaffolding - plugins []model.Plugin + // fs is the filesystem that will be used by the scaffolder + fs afero.Fs // force indicates whether to scaffold controller files even if it exists or not force bool @@ -57,35 +54,38 @@ type apiScaffolder struct { // NewAPIScaffolder returns a new Scaffolder for API/controller creation operations func NewAPIScaffolder( config config.Config, - boilerplate string, res resource.Resource, force bool, - plugins []model.Plugin, -) cmdutil.Scaffolder { +) plugins.Scaffolder { return &apiScaffolder{ - config: config, - boilerplate: boilerplate, - resource: res, - plugins: plugins, - force: force, + config: config, + resource: res, + force: force, } } -// Scaffold implements Scaffolder +// InjectFS implements cmdutil.Scaffolder +func (s *apiScaffolder) InjectFS(fs afero.Fs) { + s.fs = fs +} + +// Scaffold implements cmdutil.Scaffolder func (s *apiScaffolder) Scaffold() error { fmt.Println("Writing scaffold for you to edit...") - return s.scaffold() -} -func (s *apiScaffolder) newUniverse() *model.Universe { - return model.NewUniverse( - model.WithConfig(s.config), - model.WithBoilerplate(s.boilerplate), - model.WithResource(&s.resource), + // Load the boilerplate + boilerplate, err := afero.ReadFile(s.fs, hack.DefaultBoilerplatePath) + if err != nil { + return fmt.Errorf("error scaffolding API/controller: unable to load boilerplate: %w", err) + } + + // Initialize the machinery.Scaffold that will write the files to disk + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + machinery.WithResource(&s.resource), ) -} -func (s *apiScaffolder) scaffold() error { // Keep track of these values before the update doAPI := s.resource.HasAPI() doController := s.resource.HasController() @@ -103,9 +103,7 @@ func (s *apiScaffolder) scaffold() error { } if doAPI { - - if err := machinery.NewScaffold(s.plugins...).Execute( - s.newUniverse(), + if err := scaffold.Execute( &api.Types{Force: s.force}, &api.Group{}, &samples.CRDSample{Force: s.force}, @@ -117,19 +115,16 @@ func (s *apiScaffolder) scaffold() error { return fmt.Errorf("error scaffolding APIs: %w", err) } - if err := machinery.NewScaffold().Execute( - s.newUniverse(), + if err := scaffold.Execute( &crd.Kustomization{}, &crd.KustomizeConfig{}, ); err != nil { return fmt.Errorf("error scaffolding kustomization: %v", err) } - } if doController { - if err := machinery.NewScaffold(s.plugins...).Execute( - s.newUniverse(), + if err := scaffold.Execute( &controllers.SuiteTest{Force: s.force}, &controllers.Controller{ControllerRuntimeVersion: ControllerRuntimeVersion, Force: s.force}, ); err != nil { @@ -137,8 +132,7 @@ func (s *apiScaffolder) scaffold() error { } } - if err := machinery.NewScaffold(s.plugins...).Execute( - s.newUniverse(), + if err := scaffold.Execute( &templates.MainUpdater{WireResource: doAPI, WireController: doController}, ); err != nil { return fmt.Errorf("error updating main.go: %v", err) diff --git a/pkg/plugins/golang/v2/scaffolds/edit.go b/pkg/plugins/golang/v2/scaffolds/edit.go index 4c5def9d576..fe7c9564ce7 100644 --- a/pkg/plugins/golang/v2/scaffolds/edit.go +++ b/pkg/plugins/golang/v2/scaffolds/edit.go @@ -18,32 +18,41 @@ package scaffolds import ( "fmt" - "io/ioutil" "strings" + "github.com/spf13/afero" + "sigs.k8s.io/kubebuilder/v3/pkg/config" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" ) -var _ cmdutil.Scaffolder = &editScaffolder{} +var _ plugins.Scaffolder = &editScaffolder{} type editScaffolder struct { config config.Config multigroup bool + + // fs is the filesystem that will be used by the scaffolder + fs afero.Fs } // NewEditScaffolder returns a new Scaffolder for configuration edit operations -func NewEditScaffolder(config config.Config, multigroup bool) cmdutil.Scaffolder { +func NewEditScaffolder(config config.Config, multigroup bool) plugins.Scaffolder { return &editScaffolder{ config: config, multigroup: multigroup, } } -// Scaffold implements Scaffolder +// InjectFS implements cmdutil.Scaffolder +func (s *editScaffolder) InjectFS(fs afero.Fs) { + s.fs = fs +} + +// Scaffold implements cmdutil.Scaffolder func (s *editScaffolder) Scaffold() error { filename := "Dockerfile" - bs, err := ioutil.ReadFile(filename) + bs, err := afero.ReadFile(s.fs, filename) if err != nil { return err } @@ -76,9 +85,8 @@ func (s *editScaffolder) Scaffold() error { // Check if the str is not empty, because when the file is already in desired format it will return empty string // because there is nothing to replace. if str != "" { - // false positive - // nolint:gosec - return ioutil.WriteFile(filename, []byte(str), 0644) + // TODO: instead of writing it directly, we should use the scaffolding machinery for consistency + return afero.WriteFile(s.fs, filename, []byte(str), 0644) } return nil diff --git a/pkg/plugins/golang/v2/scaffolds/init.go b/pkg/plugins/golang/v2/scaffolds/init.go index d0674f28395..57febe08ad3 100644 --- a/pkg/plugins/golang/v2/scaffolds/init.go +++ b/pkg/plugins/golang/v2/scaffolds/init.go @@ -18,11 +18,13 @@ package scaffolds import ( "fmt" - "io/ioutil" "path/filepath" + "github.com/spf13/afero" + "sigs.k8s.io/kubebuilder/v3/pkg/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault" @@ -31,8 +33,6 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/hack" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" ) const ( @@ -46,17 +46,20 @@ const ( imageName = "controller:latest" ) -var _ cmdutil.Scaffolder = &initScaffolder{} +var _ plugins.Scaffolder = &initScaffolder{} type initScaffolder struct { config config.Config boilerplatePath string license string owner string + + // fs is the filesystem that will be used by the scaffolder + fs afero.Fs } // NewInitScaffolder returns a new Scaffolder for project initialization operations -func NewInitScaffolder(config config.Config, license, owner string) cmdutil.Scaffolder { +func NewInitScaffolder(config config.Config, license, owner string) plugins.Scaffolder { return &initScaffolder{ config: config, boilerplatePath: filepath.Join("hack", "boilerplate.go.txt"), @@ -65,47 +68,56 @@ func NewInitScaffolder(config config.Config, license, owner string) cmdutil.Scaf } } -func (s *initScaffolder) newUniverse(boilerplate string) *model.Universe { - return model.NewUniverse( - model.WithConfig(s.config), - model.WithBoilerplate(boilerplate), - ) +// InjectFS implements cmdutil.Scaffolder +func (s *initScaffolder) InjectFS(fs afero.Fs) { + s.fs = fs } -// Scaffold implements Scaffolder +// Scaffold implements cmdutil.Scaffolder func (s *initScaffolder) Scaffold() error { fmt.Println("Writing scaffold for you to edit...") - return s.scaffold() -} -func (s *initScaffolder) scaffold() error { - bpFile := &hack.Boilerplate{} + // Initialize the machinery.Scaffold that will write the boilerplate file to disk + // The boilerplate file needs to be scaffolded as a separate step as it is going to + // be used by the rest of the files, even those scaffolded in this command call. + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + ) + + bpFile := &hack.Boilerplate{ + License: s.license, + Owner: s.owner, + } bpFile.Path = s.boilerplatePath - bpFile.License = s.license - bpFile.Owner = s.owner - if err := machinery.NewScaffold().Execute( - s.newUniverse(""), - bpFile, - ); err != nil { + if err := scaffold.Execute(bpFile); err != nil { return err } - boilerplate, err := ioutil.ReadFile(s.boilerplatePath) //nolint:gosec + boilerplate, err := afero.ReadFile(s.fs, s.boilerplatePath) if err != nil { return err } - return machinery.NewScaffold().Execute( - s.newUniverse(string(boilerplate)), - &templates.GitIgnore{}, + // Initialize the machinery.Scaffold that will write the files to disk + scaffold = machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + ) + + return scaffold.Execute( + &rbac.Kustomization{}, &rbac.AuthProxyRole{}, &rbac.AuthProxyRoleBinding{}, - &kdefault.ManagerAuthProxyPatch{}, &rbac.AuthProxyService{}, &rbac.AuthProxyClientRole{}, + &rbac.RoleBinding{}, + &rbac.LeaderElectionRole{}, + &rbac.LeaderElectionRoleBinding{}, + &manager.Kustomization{}, &manager.Config{Image: imageName}, &templates.Main{}, &templates.GoMod{ControllerRuntimeVersion: ControllerRuntimeVersion}, + &templates.GitIgnore{}, &templates.Makefile{ Image: imageName, BoilerplatePath: s.boilerplatePath, @@ -114,16 +126,12 @@ func (s *initScaffolder) scaffold() error { }, &templates.Dockerfile{}, &kdefault.Kustomization{}, + &kdefault.ManagerAuthProxyPatch{}, &kdefault.ManagerWebhookPatch{}, - &rbac.RoleBinding{}, - &rbac.LeaderElectionRole{}, - &rbac.LeaderElectionRoleBinding{}, - &rbac.Kustomization{}, - &manager.Kustomization{}, + &kdefault.WebhookCAInjectionPatch{}, &webhook.Kustomization{}, &webhook.KustomizeConfig{}, &webhook.Service{}, - &kdefault.WebhookCAInjectionPatch{}, &prometheus.Kustomization{}, &prometheus.Monitor{}, &certmanager.Certificate{}, diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/group.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/group.go index bccba858d49..ba9f21f6acc 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/group.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/group.go @@ -19,17 +19,17 @@ package api import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Group{} +var _ machinery.Template = &Group{} // Group scaffolds the file that defines the registration methods for a certain group and version type Group struct { - file.TemplateMixin - file.MultiGroupMixin - file.BoilerplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go index cd0907121cf..d80e5f33b93 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go @@ -20,18 +20,18 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Types{} +var _ machinery.Template = &Types{} // Types scaffolds the file that defines the schema for a CRD // nolint:maligned type Types struct { - file.TemplateMixin - file.MultiGroupMixin - file.BoilerplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin Force bool } @@ -51,9 +51,9 @@ func (f *Types) SetTemplateDefaults() error { f.TemplateBody = typesTemplate if f.Force { - f.IfExistsAction = file.Overwrite + f.IfExistsAction = machinery.OverwriteFile } else { - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error } return nil diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/webhook.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/webhook.go index 806a37d5524..5def3986d32 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/webhook.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/webhook.go @@ -21,17 +21,17 @@ import ( "path/filepath" "strings" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Webhook{} +var _ machinery.Template = &Webhook{} // Webhook scaffolds the file that defines a webhook for a CRD or a builtin resource type Webhook struct { // nolint:maligned - file.TemplateMixin - file.MultiGroupMixin - file.BoilerplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin // Is the Group domain for the Resource replacing '.' with '-' QualifiedGroupWithDash string @@ -58,7 +58,7 @@ func (f *Webhook) SetTemplateDefaults() error { } f.TemplateBody = webhookTemplate - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error f.QualifiedGroupWithDash = strings.Replace(f.Resource.QualifiedGroup(), ".", "-", -1) diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/certificate.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/certificate.go index c8ced013fc8..4d17b8dff60 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/certificate.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/certificate.go @@ -19,14 +19,14 @@ package certmanager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Certificate{} +var _ machinery.Template = &Certificate{} // Certificate scaffolds a file that defines the issuer CR and the certificate CR type Certificate struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomization.go index 7437ab18bf1..78b2c5e9484 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomization.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomization.go @@ -19,14 +19,14 @@ package certmanager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Kustomization{} +var _ machinery.Template = &Kustomization{} // Kustomization scaffolds a file that defines the kustomization scheme for the certmanager folder type Kustomization struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go index 8eada694a10..e7dbcf8986d 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go @@ -19,14 +19,14 @@ package certmanager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &KustomizeConfig{} +var _ machinery.Template = &KustomizeConfig{} // KustomizeConfig scaffolds a file that configures the kustomization for the certmanager folder type KustomizeConfig struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomization.go index fd06bdf7848..37ec7556cb1 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomization.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomization.go @@ -20,16 +20,16 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Kustomization{} -var _ file.Inserter = &Kustomization{} +var _ machinery.Template = &Kustomization{} +var _ machinery.Inserter = &Kustomization{} // Kustomization scaffolds a file that defines the kustomization scheme for the crd folder type Kustomization struct { - file.TemplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.ResourceMixin } // SetTemplateDefaults implements file.Template @@ -40,9 +40,9 @@ func (f *Kustomization) SetTemplateDefaults() error { f.Path = f.Resource.Replacer().Replace(f.Path) f.TemplateBody = fmt.Sprintf(kustomizationTemplate, - file.NewMarkerFor(f.Path, resourceMarker), - file.NewMarkerFor(f.Path, webhookPatchMarker), - file.NewMarkerFor(f.Path, caInjectionPatchMarker), + machinery.NewMarkerFor(f.Path, resourceMarker), + machinery.NewMarkerFor(f.Path, webhookPatchMarker), + machinery.NewMarkerFor(f.Path, caInjectionPatchMarker), ) return nil @@ -55,11 +55,11 @@ const ( ) // GetMarkers implements file.Inserter -func (f *Kustomization) GetMarkers() []file.Marker { - return []file.Marker{ - file.NewMarkerFor(f.Path, resourceMarker), - file.NewMarkerFor(f.Path, webhookPatchMarker), - file.NewMarkerFor(f.Path, caInjectionPatchMarker), +func (f *Kustomization) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(f.Path, resourceMarker), + machinery.NewMarkerFor(f.Path, webhookPatchMarker), + machinery.NewMarkerFor(f.Path, caInjectionPatchMarker), } } @@ -73,8 +73,8 @@ const ( ) // GetCodeFragments implements file.Inserter -func (f *Kustomization) GetCodeFragments() file.CodeFragmentsMap { - fragments := make(file.CodeFragmentsMap, 3) +func (f *Kustomization) GetCodeFragments() machinery.CodeFragmentsMap { + fragments := make(machinery.CodeFragmentsMap, 3) // Generate resource code fragments res := make([]string, 0) @@ -90,13 +90,13 @@ func (f *Kustomization) GetCodeFragments() file.CodeFragmentsMap { // Only store code fragments in the map if the slices are non-empty if len(res) != 0 { - fragments[file.NewMarkerFor(f.Path, resourceMarker)] = res + fragments[machinery.NewMarkerFor(f.Path, resourceMarker)] = res } if len(webhookPatch) != 0 { - fragments[file.NewMarkerFor(f.Path, webhookPatchMarker)] = webhookPatch + fragments[machinery.NewMarkerFor(f.Path, webhookPatchMarker)] = webhookPatch } if len(caInjectionPatch) != 0 { - fragments[file.NewMarkerFor(f.Path, caInjectionPatchMarker)] = caInjectionPatch + fragments[machinery.NewMarkerFor(f.Path, caInjectionPatchMarker)] = caInjectionPatch } return fragments diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomizeconfig.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomizeconfig.go index 84025c65e3a..0013026f81c 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomizeconfig.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomizeconfig.go @@ -19,14 +19,14 @@ package crd import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &KustomizeConfig{} +var _ machinery.Template = &KustomizeConfig{} // KustomizeConfig scaffolds a file that configures the kustomization for the crd folder type KustomizeConfig struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go index 00d03f090d1..376a058a01c 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go @@ -19,15 +19,15 @@ package patches import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &EnableCAInjectionPatch{} +var _ machinery.Template = &EnableCAInjectionPatch{} // EnableCAInjectionPatch scaffolds a file that defines the patch that injects CA into the CRD type EnableCAInjectionPatch struct { - file.TemplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.ResourceMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go index 8e3fc5d051a..1472028b627 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go @@ -19,15 +19,15 @@ package patches import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &EnableWebhookPatch{} +var _ machinery.Template = &EnableWebhookPatch{} // EnableWebhookPatch scaffolds a file that defines the patch that enables conversion webhook for the CRD type EnableWebhookPatch struct { - file.TemplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.ResourceMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go index c51474d4cb5..3cee6b8595d 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go @@ -19,14 +19,14 @@ package kdefault import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &WebhookCAInjectionPatch{} +var _ machinery.Template = &WebhookCAInjectionPatch{} // WebhookCAInjectionPatch scaffolds a file that defines the patch that adds annotation to webhooks type WebhookCAInjectionPatch struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template @@ -37,7 +37,7 @@ func (f *WebhookCAInjectionPatch) SetTemplateDefaults() error { f.TemplateBody = injectCAPatchTemplate - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error return nil } diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/kustomization.go index 1250bcfcdaf..68ecc351cd7 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/kustomization.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/kustomization.go @@ -21,15 +21,15 @@ import ( "path/filepath" "strings" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Kustomization{} +var _ machinery.Template = &Kustomization{} // Kustomization scaffolds a file that defines the kustomization scheme for the default overlay folder type Kustomization struct { - file.TemplateMixin - file.ProjectNameMixin + machinery.TemplateMixin + machinery.ProjectNameMixin } // SetTemplateDefaults implements file.Template @@ -40,7 +40,7 @@ func (f *Kustomization) SetTemplateDefaults() error { f.TemplateBody = kustomizeTemplate - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error if f.ProjectName == "" { // Use directory name as project name, which will be empty if the project version is < v3. diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go index 8f8be890a7e..7d49171c16f 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go @@ -19,14 +19,14 @@ package kdefault import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &ManagerAuthProxyPatch{} +var _ machinery.Template = &ManagerAuthProxyPatch{} // ManagerAuthProxyPatch scaffolds a file that defines the patch that enables prometheus metrics for the manager type ManagerAuthProxyPatch struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template @@ -37,7 +37,7 @@ func (f *ManagerAuthProxyPatch) SetTemplateDefaults() error { f.TemplateBody = kustomizeAuthProxyPatchTemplate - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error return nil } diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go index 65e34c7d793..a9e0844bf18 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go @@ -19,14 +19,14 @@ package kdefault import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &ManagerWebhookPatch{} +var _ machinery.Template = &ManagerWebhookPatch{} // ManagerWebhookPatch scaffolds a file that defines the patch that enables webhooks on the manager type ManagerWebhookPatch struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/config.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/config.go index 0a1bc1cdeb7..1e08e134923 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/config.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/config.go @@ -19,14 +19,14 @@ package manager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Config{} +var _ machinery.Template = &Config{} // Config scaffolds a file that defines the namespace and the manager deployment type Config struct { - file.TemplateMixin + machinery.TemplateMixin // Image is controller manager image name Image string diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/kustomization.go index 1832137b95b..f8d5ecd7ec9 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/kustomization.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/kustomization.go @@ -19,14 +19,14 @@ package manager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Kustomization{} +var _ machinery.Template = &Kustomization{} // Kustomization scaffolds a file that defines the kustomization scheme for the manager folder type Kustomization struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template @@ -37,7 +37,7 @@ func (f *Kustomization) SetTemplateDefaults() error { f.TemplateBody = kustomizeManagerTemplate - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error return nil } diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/kustomization.go index 16b9bb4b563..c271a6a3dbb 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/kustomization.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/kustomization.go @@ -19,14 +19,14 @@ package prometheus import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Kustomization{} +var _ machinery.Template = &Kustomization{} // Kustomization scaffolds a file that defines the kustomization scheme for the prometheus folder type Kustomization struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/monitor.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/monitor.go index 4ee7d32c1c2..41870f6850a 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/monitor.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/monitor.go @@ -19,14 +19,14 @@ package prometheus import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Monitor{} +var _ machinery.Template = &Monitor{} // Monitor scaffolds a file that defines the prometheus service monitor type Monitor struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go index 2b6d77e596f..1eee0af2031 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go @@ -19,14 +19,14 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &AuthProxyClientRole{} +var _ machinery.Template = &AuthProxyClientRole{} // AuthProxyClientRole scaffolds a file that defines the role for the metrics reader type AuthProxyClientRole struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role.go index 01b8413b855..df22ef8dc39 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role.go @@ -19,14 +19,14 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &AuthProxyRole{} +var _ machinery.Template = &AuthProxyRole{} // AuthProxyRole scaffolds a file that defines the role for the auth proxy type AuthProxyRole struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go index 1aaf9ebbf85..eafc45f6ee9 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go @@ -19,14 +19,14 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &AuthProxyRoleBinding{} +var _ machinery.Template = &AuthProxyRoleBinding{} // AuthProxyRoleBinding scaffolds a file that defines the role binding for the auth proxy type AuthProxyRoleBinding struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_service.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_service.go index e84ec0e322c..6287d360ebb 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_service.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_service.go @@ -19,14 +19,14 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &AuthProxyService{} +var _ machinery.Template = &AuthProxyService{} // AuthProxyService scaffolds a file that defines the service for the auth proxy type AuthProxyService struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_editor_role.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_editor_role.go index 3de358308bd..7024549629d 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_editor_role.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_editor_role.go @@ -19,15 +19,15 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &CRDEditorRole{} +var _ machinery.Template = &CRDEditorRole{} // CRDEditorRole scaffolds a file that defines the role that allows to edit plurals type CRDEditorRole struct { - file.TemplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.ResourceMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_viewer_role.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_viewer_role.go index 5898a8fe334..74177476661 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_viewer_role.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_viewer_role.go @@ -19,15 +19,15 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &CRDViewerRole{} +var _ machinery.Template = &CRDViewerRole{} // CRDViewerRole scaffolds a file that defines the role that allows to view plurals type CRDViewerRole struct { - file.TemplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.ResourceMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/kustomization.go index d8d9b41b180..f5b164e5b79 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/kustomization.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/kustomization.go @@ -19,14 +19,14 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Kustomization{} +var _ machinery.Template = &Kustomization{} // Kustomization scaffolds a file that defines the kustomization scheme for the rbac folder type Kustomization struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template @@ -37,7 +37,7 @@ func (f *Kustomization) SetTemplateDefaults() error { f.TemplateBody = kustomizeRBACTemplate - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error return nil } diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role.go index b787f441f59..6de4d48b784 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role.go @@ -19,14 +19,14 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &LeaderElectionRole{} +var _ machinery.Template = &LeaderElectionRole{} // LeaderElectionRole scaffolds a file that defines the role that allows leader election type LeaderElectionRole struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go index f196868a3f8..9dd75b7ff6b 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go @@ -19,14 +19,14 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &LeaderElectionRoleBinding{} +var _ machinery.Template = &LeaderElectionRoleBinding{} // LeaderElectionRoleBinding scaffolds a file that defines the role binding that allows leader election type LeaderElectionRoleBinding struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/role_binding.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/role_binding.go index 6786223d648..0cc6687e8c3 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/role_binding.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/role_binding.go @@ -19,14 +19,14 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &RoleBinding{} +var _ machinery.Template = &RoleBinding{} // RoleBinding scaffolds a file that defines the role binding for the manager type RoleBinding struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples/crd_sample.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples/crd_sample.go index b9b349ff336..af9e29f6f6c 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples/crd_sample.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples/crd_sample.go @@ -19,15 +19,15 @@ package samples import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &CRDSample{} +var _ machinery.Template = &CRDSample{} // CRDSample scaffolds a file that defines a sample manifest for the CRD type CRDSample struct { - file.TemplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.ResourceMixin Force bool } @@ -40,9 +40,9 @@ func (f *CRDSample) SetTemplateDefaults() error { f.Path = f.Resource.Replacer().Replace(f.Path) if f.Force { - f.IfExistsAction = file.Overwrite + f.IfExistsAction = machinery.OverwriteFile } else { - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error } f.TemplateBody = crdSampleTemplate diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomization.go index c16ee89b9f6..7157dd8380c 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomization.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomization.go @@ -19,14 +19,14 @@ package webhook import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Kustomization{} +var _ machinery.Template = &Kustomization{} // Kustomization scaffolds a file that defines the kustomization scheme for the webhook folder type Kustomization struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template @@ -37,7 +37,7 @@ func (f *Kustomization) SetTemplateDefaults() error { f.TemplateBody = kustomizeWebhookTemplate - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error return nil } diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomizeconfig.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomizeconfig.go index 1b43d4ee825..ac2c92cc89e 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomizeconfig.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomizeconfig.go @@ -19,14 +19,14 @@ package webhook import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &KustomizeConfig{} +var _ machinery.Template = &KustomizeConfig{} // KustomizeConfig scaffolds a file that configures the kustomization for the webhook folder type KustomizeConfig struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template @@ -37,7 +37,7 @@ func (f *KustomizeConfig) SetTemplateDefaults() error { f.TemplateBody = kustomizeConfigWebhookTemplate - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error return nil } diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/service.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/service.go index 3a2689c4d42..7783aa136c4 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/service.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/service.go @@ -19,14 +19,14 @@ package webhook import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Service{} +var _ machinery.Template = &Service{} // Service scaffolds a file that defines the webhook service type Service struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template @@ -37,7 +37,7 @@ func (f *Service) SetTemplateDefaults() error { f.TemplateBody = serviceTemplate - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error return nil } diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go index ee059001455..3d400d399e1 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go @@ -20,18 +20,18 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Controller{} +var _ machinery.Template = &Controller{} // Controller scaffolds the file that defines the controller for a CRD or a builtin resource // nolint:maligned type Controller struct { - file.TemplateMixin - file.MultiGroupMixin - file.BoilerplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin ControllerRuntimeVersion string @@ -53,9 +53,9 @@ func (f *Controller) SetTemplateDefaults() error { f.TemplateBody = controllerTemplate if f.Force { - f.IfExistsAction = file.Overwrite + f.IfExistsAction = machinery.OverwriteFile } else { - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error } return nil diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go index 61bd1d584d7..31cef7a8650 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go @@ -20,19 +20,19 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &SuiteTest{} -var _ file.Inserter = &SuiteTest{} +var _ machinery.Template = &SuiteTest{} +var _ machinery.Inserter = &SuiteTest{} // SuiteTest scaffolds the file that sets up the controller tests // nolint:maligned type SuiteTest struct { - file.TemplateMixin - file.MultiGroupMixin - file.BoilerplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin // CRDDirectoryRelativePath define the Path for the CRD CRDDirectoryRelativePath string @@ -52,8 +52,8 @@ func (f *SuiteTest) SetTemplateDefaults() error { f.Path = f.Resource.Replacer().Replace(f.Path) f.TemplateBody = fmt.Sprintf(controllerSuiteTestTemplate, - file.NewMarkerFor(f.Path, importMarker), - file.NewMarkerFor(f.Path, addSchemeMarker), + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), ) // If is multigroup the path needs to be ../../ since it has @@ -64,7 +64,7 @@ func (f *SuiteTest) SetTemplateDefaults() error { } if f.Force { - f.IfExistsAction = file.Overwrite + f.IfExistsAction = machinery.OverwriteFile } return nil @@ -76,10 +76,10 @@ const ( ) // GetMarkers implements file.Inserter -func (f *SuiteTest) GetMarkers() []file.Marker { - return []file.Marker{ - file.NewMarkerFor(f.Path, importMarker), - file.NewMarkerFor(f.Path, addSchemeMarker), +func (f *SuiteTest) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), } } @@ -93,8 +93,8 @@ Expect(err).NotTo(HaveOccurred()) ) // GetCodeFragments implements file.Inserter -func (f *SuiteTest) GetCodeFragments() file.CodeFragmentsMap { - fragments := make(file.CodeFragmentsMap, 2) +func (f *SuiteTest) GetCodeFragments() machinery.CodeFragmentsMap { + fragments := make(machinery.CodeFragmentsMap, 2) // Generate import code fragments imports := make([]string, 0) @@ -110,10 +110,10 @@ func (f *SuiteTest) GetCodeFragments() file.CodeFragmentsMap { // Only store code fragments in the map if the slices are non-empty if len(imports) != 0 { - fragments[file.NewMarkerFor(f.Path, importMarker)] = imports + fragments[machinery.NewMarkerFor(f.Path, importMarker)] = imports } if len(addScheme) != 0 { - fragments[file.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme + fragments[machinery.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme } return fragments diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/dockerfile.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/dockerfile.go index 1d20cafef78..38e4c47167c 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/dockerfile.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/dockerfile.go @@ -17,14 +17,14 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Dockerfile{} +var _ machinery.Template = &Dockerfile{} // Dockerfile scaffolds a file that defines the containerized build process type Dockerfile struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/gitignore.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/gitignore.go index 4505799177b..a60f46d6760 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/gitignore.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/gitignore.go @@ -17,14 +17,14 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &GitIgnore{} +var _ machinery.Template = &GitIgnore{} // GitIgnore scaffolds a file that defines which files should be ignored by git type GitIgnore struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/gomod.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/gomod.go index f40e8eda54d..e369bc98567 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/gomod.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/gomod.go @@ -17,15 +17,15 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &GoMod{} +var _ machinery.Template = &GoMod{} // GoMod scaffolds a file that defines the project dependencies type GoMod struct { - file.TemplateMixin - file.RepositoryMixin + machinery.TemplateMixin + machinery.RepositoryMixin ControllerRuntimeVersion string } @@ -38,7 +38,7 @@ func (f *GoMod) SetTemplateDefaults() error { f.TemplateBody = goModTemplate - f.IfExistsAction = file.Overwrite + f.IfExistsAction = machinery.OverwriteFile return nil } diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/hack/boilerplate.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/hack/boilerplate.go index 561d4befbdc..1d07c79b45a 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/hack/boilerplate.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/hack/boilerplate.go @@ -21,15 +21,18 @@ import ( "path/filepath" "time" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Boilerplate{} +// DefaultBoilerplatePath is the default path to the boilerplate file +var DefaultBoilerplatePath = filepath.Join("hack", "boilerplate.go.txt") + +var _ machinery.Template = &Boilerplate{} // Boilerplate scaffolds a file that defines the common header for the rest of the files type Boilerplate struct { - file.TemplateMixin - file.BoilerplateMixin + machinery.TemplateMixin + machinery.BoilerplateMixin // License is the License type to write License string @@ -62,7 +65,7 @@ func (f Boilerplate) Validate() error { // SetTemplateDefaults implements file.Template func (f *Boilerplate) SetTemplateDefaults() error { if f.Path == "" { - f.Path = filepath.Join("hack", "boilerplate.go.txt") + f.Path = DefaultBoilerplatePath } if f.License == "" { diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/main.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/main.go index f234d80970a..c2bd6928d85 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/main.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/main.go @@ -20,19 +20,19 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) const defaultMainPath = "main.go" -var _ file.Template = &Main{} +var _ machinery.Template = &Main{} // Main scaffolds a file that defines the controller manager entry point type Main struct { - file.TemplateMixin - file.BoilerplateMixin - file.DomainMixin - file.RepositoryMixin + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.DomainMixin + machinery.RepositoryMixin } // SetTemplateDefaults implements file.Template @@ -42,21 +42,21 @@ func (f *Main) SetTemplateDefaults() error { } f.TemplateBody = fmt.Sprintf(mainTemplate, - file.NewMarkerFor(f.Path, importMarker), - file.NewMarkerFor(f.Path, addSchemeMarker), - file.NewMarkerFor(f.Path, setupMarker), + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), + machinery.NewMarkerFor(f.Path, setupMarker), ) return nil } -var _ file.Inserter = &MainUpdater{} +var _ machinery.Inserter = &MainUpdater{} // MainUpdater updates main.go to run Controllers type MainUpdater struct { //nolint:maligned - file.RepositoryMixin - file.MultiGroupMixin - file.ResourceMixin + machinery.RepositoryMixin + machinery.MultiGroupMixin + machinery.ResourceMixin // Flags to indicate which parts need to be included when updating the file WireResource, WireController, WireWebhook bool @@ -68,8 +68,8 @@ func (*MainUpdater) GetPath() string { } // GetIfExistsAction implements file.Builder -func (*MainUpdater) GetIfExistsAction() file.IfExistsAction { - return file.Overwrite +func (*MainUpdater) GetIfExistsAction() machinery.IfExistsAction { + return machinery.OverwriteFile } const ( @@ -79,11 +79,11 @@ const ( ) // GetMarkers implements file.Inserter -func (f *MainUpdater) GetMarkers() []file.Marker { - return []file.Marker{ - file.NewMarkerFor(defaultMainPath, importMarker), - file.NewMarkerFor(defaultMainPath, addSchemeMarker), - file.NewMarkerFor(defaultMainPath, setupMarker), +func (f *MainUpdater) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(defaultMainPath, importMarker), + machinery.NewMarkerFor(defaultMainPath, addSchemeMarker), + machinery.NewMarkerFor(defaultMainPath, setupMarker), } } @@ -122,8 +122,8 @@ const ( ) // GetCodeFragments implements file.Inserter -func (f *MainUpdater) GetCodeFragments() file.CodeFragmentsMap { - fragments := make(file.CodeFragmentsMap, 3) +func (f *MainUpdater) GetCodeFragments() machinery.CodeFragmentsMap { + fragments := make(machinery.CodeFragmentsMap, 3) // If resource is not being provided we are creating the file, not updating it if f.Resource == nil { @@ -169,13 +169,13 @@ func (f *MainUpdater) GetCodeFragments() file.CodeFragmentsMap { // Only store code fragments in the map if the slices are non-empty if len(imports) != 0 { - fragments[file.NewMarkerFor(defaultMainPath, importMarker)] = imports + fragments[machinery.NewMarkerFor(defaultMainPath, importMarker)] = imports } if len(addScheme) != 0 { - fragments[file.NewMarkerFor(defaultMainPath, addSchemeMarker)] = addScheme + fragments[machinery.NewMarkerFor(defaultMainPath, addSchemeMarker)] = addScheme } if len(setup) != 0 { - fragments[file.NewMarkerFor(defaultMainPath, setupMarker)] = setup + fragments[machinery.NewMarkerFor(defaultMainPath, setupMarker)] = setup } return fragments diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/makefile.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/makefile.go index cea72da1141..e799bd05cc9 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/makefile.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/makefile.go @@ -17,14 +17,14 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Makefile{} +var _ machinery.Template = &Makefile{} // Makefile scaffolds a file that defines project management CLI commands type Makefile struct { - file.TemplateMixin + machinery.TemplateMixin // Image is controller manager image name Image string @@ -44,7 +44,7 @@ func (f *Makefile) SetTemplateDefaults() error { f.TemplateBody = makefileTemplate - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error if f.Image == "" { f.Image = "controller:latest" diff --git a/pkg/plugins/golang/v2/scaffolds/webhook.go b/pkg/plugins/golang/v2/scaffolds/webhook.go index d60175f8c13..c7ac711a127 100644 --- a/pkg/plugins/golang/v2/scaffolds/webhook.go +++ b/pkg/plugins/golang/v2/scaffolds/webhook.go @@ -19,57 +19,62 @@ package scaffolds import ( "fmt" + "github.com/spf13/afero" + "sigs.k8s.io/kubebuilder/v3/pkg/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/api" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/hack" ) -var _ cmdutil.Scaffolder = &webhookScaffolder{} +var _ plugins.Scaffolder = &webhookScaffolder{} type webhookScaffolder struct { - config config.Config - boilerplate string - resource resource.Resource + config config.Config + resource resource.Resource + + // fs is the filesystem that will be used by the scaffolder + fs afero.Fs } // NewWebhookScaffolder returns a new Scaffolder for v2 webhook creation operations -func NewWebhookScaffolder( - config config.Config, - boilerplate string, - resource resource.Resource, -) cmdutil.Scaffolder { +func NewWebhookScaffolder(config config.Config, resource resource.Resource) plugins.Scaffolder { return &webhookScaffolder{ - config: config, - boilerplate: boilerplate, - resource: resource, + config: config, + resource: resource, } } -// Scaffold implements Scaffolder +// InjectFS implements cmdutil.Scaffolder +func (s *webhookScaffolder) InjectFS(fs afero.Fs) { + s.fs = fs +} + +// Scaffold implements cmdutil.Scaffolder func (s *webhookScaffolder) Scaffold() error { fmt.Println("Writing scaffold for you to edit...") - return s.scaffold() -} -func (s *webhookScaffolder) newUniverse() *model.Universe { - return model.NewUniverse( - model.WithConfig(s.config), - model.WithBoilerplate(s.boilerplate), - model.WithResource(&s.resource), + // Load the boilerplate + boilerplate, err := afero.ReadFile(s.fs, hack.DefaultBoilerplatePath) + if err != nil { + return fmt.Errorf("error scaffolding webhook: unable to load boilerplate: %w", err) + } + + // Initialize the machinery.Scaffold that will write the files to disk + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + machinery.WithResource(&s.resource), ) -} -func (s *webhookScaffolder) scaffold() error { if err := s.config.UpdateResource(s.resource); err != nil { return fmt.Errorf("error updating resource: %w", err) } - if err := machinery.NewScaffold().Execute( - s.newUniverse(), + if err := scaffold.Execute( &api.Webhook{}, &templates.MainUpdater{WireWebhook: true}, ); err != nil { diff --git a/pkg/plugins/golang/v2/webhook.go b/pkg/plugins/golang/v2/webhook.go index 735c48c68df..b921fd2a7ef 100644 --- a/pkg/plugins/golang/v2/webhook.go +++ b/pkg/plugins/golang/v2/webhook.go @@ -18,59 +18,50 @@ package v2 import ( "fmt" - "io/ioutil" - "path/filepath" + "github.com/spf13/afero" "github.com/spf13/pflag" - newconfig "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config" cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" ) +var _ plugin.CreateWebhookSubcommand = &createWebhookSubcommand{} + type createWebhookSubcommand struct { - config newconfig.Config + config config.Config // For help text. commandName string - options *Options + options *goPlugin.Options - resource resource.Resource + resource *resource.Resource } -var ( - _ plugin.CreateWebhookSubcommand = &createWebhookSubcommand{} - _ cmdutil.RunOptions = &createWebhookSubcommand{} -) +func (p *createWebhookSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + p.commandName = cliMeta.CommandName -func (p *createWebhookSubcommand) UpdateContext(ctx *plugin.Context) { - ctx.Description = `Scaffold a webhook for an API resource. You can choose to scaffold defaulting, + subcmdMeta.Description = `Scaffold a webhook for an API resource. You can choose to scaffold defaulting, validating and (or) conversion webhooks. ` - ctx.Examples = fmt.Sprintf(` # Create defaulting and validating webhooks for CRD of group ship, version v1beta1 + subcmdMeta.Examples = fmt.Sprintf(` # Create defaulting and validating webhooks for CRD of group ship, version v1beta1 # and kind Frigate. - %s create webhook --group ship --version v1beta1 --kind Frigate --defaulting --programmatic-validation + %[1]s create webhook --group ship --version v1beta1 --kind Frigate --defaulting --programmatic-validation # Create conversion webhook for CRD of group shio, version v1beta1 and kind Frigate. - %s create webhook --group ship --version v1beta1 --kind Frigate --conversion -`, - ctx.CommandName, ctx.CommandName) - - p.commandName = ctx.CommandName + %[1]s create webhook --group ship --version v1beta1 --kind Frigate --conversion +`, cliMeta.CommandName) } func (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) { - p.options = &Options{} - fs.StringVar(&p.options.Group, "group", "", "resource Group") - p.options.Domain = p.config.GetDomain() - fs.StringVar(&p.options.Version, "version", "", "resource Version") - fs.StringVar(&p.options.Kind, "kind", "", "resource Kind") + p.options = &goPlugin.Options{WebhookVersion: "v1beta1"} + fs.StringVar(&p.options.Plural, "resource", "", "resource irregular plural form") - p.options.WebhookVersion = "v1beta1" fs.BoolVar(&p.options.DoDefaulting, "defaulting", false, "if set, scaffold the defaulting webhook") fs.BoolVar(&p.options.DoValidation, "programmatic-validation", false, @@ -79,22 +70,21 @@ func (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) { "if set, scaffold the conversion webhook") } -func (p *createWebhookSubcommand) InjectConfig(c newconfig.Config) { +func (p *createWebhookSubcommand) InjectConfig(c config.Config) error { p.config = c -} - -func (p *createWebhookSubcommand) Run() error { - // Create the resource from the options - p.resource = p.options.NewResource(p.config) - return cmdutil.Run(p) + return nil } -func (p *createWebhookSubcommand) Validate() error { - if err := p.options.Validate(); err != nil { - return err +func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error { + p.resource = res + + if p.resource.Group == "" { + return fmt.Errorf("group cannot be empty") } + p.options.UpdateResource(p.resource, p.config) + if err := p.resource.Validate(); err != nil { return err } @@ -120,16 +110,8 @@ func (p *createWebhookSubcommand) Validate() error { return nil } -func (p *createWebhookSubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { - // Load the boilerplate - bp, err := ioutil.ReadFile(filepath.Join("hack", "boilerplate.go.txt")) // nolint:gosec - if err != nil { - return nil, fmt.Errorf("unable to load boilerplate: %v", err) - } - - return scaffolds.NewWebhookScaffolder(p.config, string(bp), p.resource), nil -} - -func (p *createWebhookSubcommand) PostScaffold() error { - return nil +func (p *createWebhookSubcommand) Scaffold(fs afero.Fs) error { + scaffolder := scaffolds.NewWebhookScaffolder(p.config, *p.resource) + scaffolder.InjectFS(fs) + return scaffolder.Scaffold() } diff --git a/pkg/plugins/golang/v3/api.go b/pkg/plugins/golang/v3/api.go index f71bc7e5e4f..1b51e67342a 100644 --- a/pkg/plugins/golang/v3/api.go +++ b/pkg/plugins/golang/v3/api.go @@ -20,30 +20,20 @@ import ( "bufio" "errors" "fmt" - "io/ioutil" "os" - "path/filepath" - "strings" + "github.com/spf13/afero" "github.com/spf13/pflag" "sigs.k8s.io/kubebuilder/v3/pkg/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery/util" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/util" - "sigs.k8s.io/kubebuilder/v3/plugins/addon" ) const ( - // KbDeclarativePatternVersion is the sigs.k8s.io/kubebuilder-declarative-pattern version - // (used only to gen api with --pattern=addon) - // TODO: remove this when a better solution for using addons is implemented. - KbDeclarativePatternVersion = "b84d99da021778217217885dd9582ed3cc879ebe" - // defaultCRDVersion is the default CRD API version to scaffold. defaultCRDVersion = "v1" ) @@ -51,15 +41,14 @@ const ( // DefaultMainPath is default file path of main.go const DefaultMainPath = "main.go" +var _ plugin.CreateAPISubcommand = &createAPISubcommand{} + type createAPISubcommand struct { config config.Config - // pattern indicates that we should use a plugin to build according to a pattern - pattern string - options *goPlugin.Options - resource resource.Resource + resource *resource.Resource // Check if we have to scaffold resource and/or controller resourceFlag *pflag.Flag @@ -72,13 +61,8 @@ type createAPISubcommand struct { runMake bool } -var ( - _ plugin.CreateAPISubcommand = &createAPISubcommand{} - _ cmdutil.RunOptions = &createAPISubcommand{} -) - -func (p createAPISubcommand) UpdateContext(ctx *plugin.Context) { - ctx.Description = `Scaffold a Kubernetes API by creating a Resource definition and / or a Controller. +func (p *createAPISubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + subcmdMeta.Description = `Scaffold a Kubernetes API by creating a Resource definition and / or a Controller. create resource will prompt the user for if it should scaffold the Resource and / or Controller. To only scaffold a Controller for an existing Resource, select "n" for Resource. To only define @@ -86,7 +70,7 @@ the schema for a Resource without writing a Controller, select "n" for Controlle After the scaffold is written, api will run make on the project. ` - ctx.Examples = fmt.Sprintf(` # Create a frigates API with Group: ship, Version: v1beta1 and Kind: Frigate + subcmdMeta.Examples = fmt.Sprintf(` # Create a frigates API with Group: ship, Version: v1beta1 and Kind: Frigate %s create api --group ship --version v1beta1 --kind Frigate # Edit the API Scheme @@ -103,27 +87,17 @@ After the scaffold is written, api will run make on the project. # Regenerate code and run against the Kubernetes cluster configured by ~/.kube/config make run - `, - ctx.CommandName) + `, cliMeta.CommandName) } func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { fs.BoolVar(&p.runMake, "make", true, "if true, run `make generate` after generating files") - // TODO: remove this when a better solution for using addons is implemented. - if os.Getenv("KUBEBUILDER_ENABLE_PLUGINS") != "" { - fs.StringVar(&p.pattern, "pattern", "", - "generates an API following an extension pattern (addon)") - } - fs.BoolVar(&p.force, "force", false, "attempt to create resource even if it already exists") p.options = &goPlugin.Options{} - fs.StringVar(&p.options.Group, "group", "", "resource Group") - p.options.Domain = p.config.GetDomain() - fs.StringVar(&p.options.Version, "version", "", "resource Version") - fs.StringVar(&p.options.Kind, "kind", "", "resource Kind") + fs.StringVar(&p.options.Plural, "plural", "", "resource irregular plural form") fs.BoolVar(&p.options.DoAPI, "resource", true, @@ -138,13 +112,18 @@ func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { p.controllerFlag = fs.Lookup("controller") } -func (p *createAPISubcommand) InjectConfig(c config.Config) { +func (p *createAPISubcommand) InjectConfig(c config.Config) error { p.config = c + + return nil } -func (p *createAPISubcommand) Run() error { +func (p *createAPISubcommand) InjectResource(res *resource.Resource) error { + p.resource = res + // TODO: re-evaluate whether y/n input still makes sense. We should probably always // scaffold the resource and controller. + // Ask for API and Controller if not specified reader := bufio.NewReader(os.Stdin) if !p.resourceFlag.Changed { fmt.Println("Create Resource [y/n]") @@ -155,30 +134,16 @@ func (p *createAPISubcommand) Run() error { p.options.DoController = util.YesNo(reader) } - // Create the resource from the options - p.resource = p.options.NewResource(p.config) - - return cmdutil.Run(p) -} - -func (p *createAPISubcommand) Validate() error { - if err := p.options.Validate(); err != nil { - return err - } + p.options.UpdateResource(p.resource, p.config) if err := p.resource.Validate(); err != nil { return err } - // check if main.go is present in the root directory - if _, err := os.Stat(DefaultMainPath); os.IsNotExist(err) { - return fmt.Errorf("%s file should present in the root directory", DefaultMainPath) - } - // In case we want to scaffold a resource API we need to do some checks if p.options.DoAPI { // Check that resource doesn't have the API scaffolded or flag force was set - if res, err := p.config.GetResource(p.resource.GVK); err == nil && res.HasAPI() && !p.force { + if r, err := p.config.GetResource(p.resource.GVK); err == nil && r.HasAPI() && !p.force { return errors.New("API resource already exists") } @@ -198,46 +163,25 @@ func (p *createAPISubcommand) Validate() error { return nil } -func (p *createAPISubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { - // Load the boilerplate - bp, err := ioutil.ReadFile(filepath.Join("hack", "boilerplate.go.txt")) // nolint:gosec - if err != nil { - return nil, fmt.Errorf("unable to load boilerplate: %v", err) +func (p *createAPISubcommand) PreScaffold(afero.Fs) error { + // check if main.go is present in the root directory + if _, err := os.Stat(DefaultMainPath); os.IsNotExist(err) { + return fmt.Errorf("%s file should present in the root directory", DefaultMainPath) } - // Load the requested plugins - plugins := make([]model.Plugin, 0) - switch strings.ToLower(p.pattern) { - case "": - // Default pattern - case "addon": - plugins = append(plugins, &addon.Plugin{}) - default: - return nil, fmt.Errorf("unknown pattern %q", p.pattern) - } + return nil +} - return scaffolds.NewAPIScaffolder(p.config, string(bp), p.resource, p.force, plugins), nil +func (p *createAPISubcommand) Scaffold(fs afero.Fs) error { + scaffolder := scaffolds.NewAPIScaffolder(p.config, *p.resource, p.force) + scaffolder.InjectFS(fs) + return scaffolder.Scaffold() } func (p *createAPISubcommand) PostScaffold() error { - // Load the requested plugins - switch strings.ToLower(p.pattern) { - case "": - // Default pattern - case "addon": - // Ensure that we are pinning sigs.k8s.io/kubebuilder-declarative-pattern version - // TODO: either find a better way to inject this version (ex. tools.go). - err := util.RunCmd("Get kubebuilder-declarative-pattern dependency", "go", "get", - "sigs.k8s.io/kubebuilder-declarative-pattern@"+KbDeclarativePatternVersion) - if err != nil { - return err - } - default: - return fmt.Errorf("unknown pattern %q", p.pattern) - } - - if p.runMake { // TODO: check if API was scaffolded + if p.runMake && p.resource.HasAPI() { return util.RunCmd("Running make", "make", "generate") } + return nil } diff --git a/pkg/plugins/golang/v3/edit.go b/pkg/plugins/golang/v3/edit.go index 503ed2fafe3..cdec7bdc75f 100644 --- a/pkg/plugins/golang/v3/edit.go +++ b/pkg/plugins/golang/v3/edit.go @@ -19,56 +19,47 @@ package v3 import ( "fmt" + "github.com/spf13/afero" "github.com/spf13/pflag" "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" ) +var _ plugin.EditSubcommand = &editSubcommand{} + type editSubcommand struct { config config.Config multigroup bool } -var ( - _ plugin.EditSubcommand = &editSubcommand{} - _ cmdutil.RunOptions = &editSubcommand{} -) - -func (p *editSubcommand) UpdateContext(ctx *plugin.Context) { - ctx.Description = `This command will edit the project configuration. You can have single or multi group project.` - - ctx.Examples = fmt.Sprintf(`# Enable the multigroup layout - %s edit --multigroup +func (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + subcmdMeta.Description = `This command will edit the project configuration. +Features supported: + - Toggle between single or multi group projects. +` + subcmdMeta.Examples = fmt.Sprintf(`# Enable the multigroup layout + %[1]s edit --multigroup # Disable the multigroup layout - %s edit --multigroup=false - `, ctx.CommandName, ctx.CommandName) + %[1]s edit --multigroup=false + `, cliMeta.CommandName) } func (p *editSubcommand) BindFlags(fs *pflag.FlagSet) { fs.BoolVar(&p.multigroup, "multigroup", false, "enable or disable multigroup layout") } -func (p *editSubcommand) InjectConfig(c config.Config) { +func (p *editSubcommand) InjectConfig(c config.Config) error { p.config = c -} -func (p *editSubcommand) Run() error { - return cmdutil.Run(p) -} - -func (p *editSubcommand) Validate() error { return nil } -func (p *editSubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { - return scaffolds.NewEditScaffolder(p.config, p.multigroup), nil -} - -func (p *editSubcommand) PostScaffold() error { - return nil +func (p *editSubcommand) Scaffold(fs afero.Fs) error { + scaffolder := scaffolds.NewEditScaffolder(p.config, p.multigroup) + scaffolder.InjectFS(fs) + return scaffolder.Scaffold() } diff --git a/pkg/plugins/golang/v3/init.go b/pkg/plugins/golang/v3/init.go index b2572c7bf4a..746ab6a478a 100644 --- a/pkg/plugins/golang/v3/init.go +++ b/pkg/plugins/golang/v3/init.go @@ -22,16 +22,18 @@ import ( "path/filepath" "strings" + "github.com/spf13/afero" "github.com/spf13/pflag" "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery/util" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/util" ) +var _ plugin.InitSubcommand = &initSubcommand{} + type initSubcommand struct { config config.Config // For help text. @@ -52,30 +54,24 @@ type initSubcommand struct { skipGoVersionCheck bool } -var ( - _ plugin.InitSubcommand = &initSubcommand{} - _ cmdutil.RunOptions = &initSubcommand{} -) +func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + p.commandName = cliMeta.CommandName -func (p *initSubcommand) UpdateContext(ctx *plugin.Context) { - ctx.Description = `Initialize a new project including vendor/ directory and Go package directories. + subcmdMeta.Description = `Initialize a new project including vendor/ directory and Go package directories. Writes the following files: - a boilerplate license file - a PROJECT file with the domain and repo - a Makefile to build the project - a go.mod with project dependencies -- a Kustomization.yaml for customizating manifests +- a Kustomization.yaml for customizing manifests - a Patch file for customizing image for manager manifests - a Patch file for enabling prometheus metrics - a main.go to run ` - ctx.Examples = fmt.Sprintf(` # Scaffold a project using the apache2 license with "The Kubernetes authors" as owners - %s init --project-version=2 --domain example.org --license apache2 --owner "The Kubernetes authors" -`, - ctx.CommandName) - - p.commandName = ctx.CommandName + subcmdMeta.Examples = fmt.Sprintf(` # Scaffold a project using the apache2 license with %[2]q as owners + %[1]s init --project-version=3-alpha --domain example.org --license apache2 --owner %[2]q +`, cliMeta.CommandName, "The Kubernetes authors") } func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) { @@ -99,26 +95,22 @@ func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) { "create a versioned ComponentConfig file, may be 'true' or 'false'") } -func (p *initSubcommand) InjectConfig(c config.Config) { - _ = c.SetLayout(plugin.KeyFor(Plugin{})) - +func (p *initSubcommand) InjectConfig(c config.Config) error { p.config = c -} -func (p *initSubcommand) Run() error { - return cmdutil.Run(p) -} + if err := p.config.SetDomain(p.domain); err != nil { + return err + } -func (p *initSubcommand) Validate() error { - // Requires go1.11+ - if !p.skipGoVersionCheck { - if err := util.ValidateGoVersion(); err != nil { - return err + // Try to guess repository if flag is not set. + if p.repo == "" { + repoPath, err := util.FindCurrentRepo() + if err != nil { + return fmt.Errorf("error finding current repository: %v", err) } + p.repo = repoPath } - - // Check if the current directory has not files or directories which does not allow to init the project - if err := checkDir(); err != nil { + if err := p.config.SetRepository(p.repo); err != nil { return err } @@ -134,36 +126,39 @@ func (p *initSubcommand) Validate() error { if err := validation.IsDNS1123Label(p.name); err != nil { return fmt.Errorf("project name (%s) is invalid: %v", p.name, err) } + if err := p.config.SetProjectName(p.name); err != nil { + return err + } - // Try to guess repository if flag is not set. - if p.repo == "" { - repoPath, err := util.FindCurrentRepo() - if err != nil { - return fmt.Errorf("error finding current repository: %v", err) + if p.componentConfig { + if err := p.config.SetComponentConfig(); err != nil { + return err } - p.repo = repoPath } return nil } -func (p *initSubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { - if err := p.config.SetDomain(p.domain); err != nil { - return nil, err - } - if err := p.config.SetRepository(p.repo); err != nil { - return nil, err - } - if err := p.config.SetProjectName(p.name); err != nil { - return nil, err - } - if p.componentConfig { - if err := p.config.SetComponentConfig(); err != nil { - return nil, err +func (p *initSubcommand) PreScaffold(afero.Fs) error { + // Requires go1.11+ + if !p.skipGoVersionCheck { + if err := util.ValidateGoVersion(); err != nil { + return err } } - return scaffolds.NewInitScaffolder(p.config, p.license, p.owner), nil + // Check if the current directory has not files or directories which does not allow to init the project + if err := checkDir(); err != nil { + return err + } + + return nil +} + +func (p *initSubcommand) Scaffold(fs afero.Fs) error { + scaffolder := scaffolds.NewInitScaffolder(p.config, p.license, p.owner) + scaffolder.InjectFS(fs) + return scaffolder.Scaffold() } func (p *initSubcommand) PostScaffold() error { diff --git a/pkg/plugins/golang/v3/scaffolds/api.go b/pkg/plugins/golang/v3/scaffolds/api.go index 1d9698cafc3..f2797f6c524 100644 --- a/pkg/plugins/golang/v3/scaffolds/api.go +++ b/pkg/plugins/golang/v3/scaffolds/api.go @@ -19,9 +19,12 @@ package scaffolds import ( "fmt" + "github.com/spf13/afero" + "sigs.k8s.io/kubebuilder/v3/pkg/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/api" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd" @@ -29,21 +32,19 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/samples" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/hack" ) -var _ cmdutil.Scaffolder = &apiScaffolder{} +var _ plugins.Scaffolder = &apiScaffolder{} // apiScaffolder contains configuration for generating scaffolding for Go type // representing the API and controller that implements the behavior for the API. type apiScaffolder struct { - config config.Config - boilerplate string - resource resource.Resource + config config.Config + resource resource.Resource - // plugins is the list of plugins we should allow to transform our generated scaffolding - plugins []model.Plugin + // fs is the filesystem that will be used by the scaffolder + fs afero.Fs // force indicates whether to scaffold controller files even if it exists or not force bool @@ -52,36 +53,38 @@ type apiScaffolder struct { // NewAPIScaffolder returns a new Scaffolder for API/controller creation operations func NewAPIScaffolder( config config.Config, - boilerplate string, res resource.Resource, force bool, - plugins []model.Plugin, -) cmdutil.Scaffolder { +) plugins.Scaffolder { return &apiScaffolder{ - config: config, - boilerplate: boilerplate, - resource: res, - plugins: plugins, - force: force, + config: config, + resource: res, + force: force, } } -// Scaffold implements Scaffolder +// InjectFS implements cmdutil.Scaffolder +func (s *apiScaffolder) InjectFS(fs afero.Fs) { + s.fs = fs +} + +// Scaffold implements cmdutil.Scaffolder func (s *apiScaffolder) Scaffold() error { fmt.Println("Writing scaffold for you to edit...") - return s.scaffold() -} -func (s *apiScaffolder) newUniverse() *model.Universe { - return model.NewUniverse( - model.WithConfig(s.config), - model.WithBoilerplate(s.boilerplate), - model.WithResource(&s.resource), + // Load the boilerplate + boilerplate, err := afero.ReadFile(s.fs, hack.DefaultBoilerplatePath) + if err != nil { + return fmt.Errorf("error scaffolding API/controller: unable to load boilerplate: %w", err) + } + + // Initialize the machinery.Scaffold that will write the files to disk + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + machinery.WithResource(&s.resource), ) -} -// TODO: re-use universe created by s.newUniverse() if possible. -func (s *apiScaffolder) scaffold() error { // Keep track of these values before the update doAPI := s.resource.HasAPI() doController := s.resource.HasController() @@ -92,8 +95,7 @@ func (s *apiScaffolder) scaffold() error { if doAPI { - if err := machinery.NewScaffold(s.plugins...).Execute( - s.newUniverse(), + if err := scaffold.Execute( &api.Types{Force: s.force}, &api.Group{}, &samples.CRDSample{Force: s.force}, @@ -105,8 +107,7 @@ func (s *apiScaffolder) scaffold() error { return fmt.Errorf("error scaffolding APIs: %v", err) } - if err := machinery.NewScaffold().Execute( - s.newUniverse(), + if err := scaffold.Execute( &crd.Kustomization{}, &crd.KustomizeConfig{}, ); err != nil { @@ -116,8 +117,7 @@ func (s *apiScaffolder) scaffold() error { } if doController { - if err := machinery.NewScaffold(s.plugins...).Execute( - s.newUniverse(), + if err := scaffold.Execute( &controllers.SuiteTest{Force: s.force}, &controllers.Controller{ControllerRuntimeVersion: ControllerRuntimeVersion, Force: s.force}, ); err != nil { @@ -125,8 +125,7 @@ func (s *apiScaffolder) scaffold() error { } } - if err := machinery.NewScaffold(s.plugins...).Execute( - s.newUniverse(), + if err := scaffold.Execute( &templates.MainUpdater{WireResource: doAPI, WireController: doController}, ); err != nil { return fmt.Errorf("error updating main.go: %v", err) diff --git a/pkg/plugins/golang/v3/scaffolds/edit.go b/pkg/plugins/golang/v3/scaffolds/edit.go index f4f92785359..e62d95c6deb 100644 --- a/pkg/plugins/golang/v3/scaffolds/edit.go +++ b/pkg/plugins/golang/v3/scaffolds/edit.go @@ -18,32 +18,41 @@ package scaffolds import ( "fmt" - "io/ioutil" "strings" + "github.com/spf13/afero" + "sigs.k8s.io/kubebuilder/v3/pkg/config" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" ) -var _ cmdutil.Scaffolder = &editScaffolder{} +var _ plugins.Scaffolder = &editScaffolder{} type editScaffolder struct { config config.Config multigroup bool + + // fs is the filesystem that will be used by the scaffolder + fs afero.Fs } // NewEditScaffolder returns a new Scaffolder for configuration edit operations -func NewEditScaffolder(config config.Config, multigroup bool) cmdutil.Scaffolder { +func NewEditScaffolder(config config.Config, multigroup bool) plugins.Scaffolder { return &editScaffolder{ config: config, multigroup: multigroup, } } -// Scaffold implements Scaffolder +// InjectFS implements cmdutil.Scaffolder +func (s *editScaffolder) InjectFS(fs afero.Fs) { + s.fs = fs +} + +// Scaffold implements cmdutil.Scaffolder func (s *editScaffolder) Scaffold() error { filename := "Dockerfile" - bs, err := ioutil.ReadFile(filename) + bs, err := afero.ReadFile(s.fs, filename) if err != nil { return err } @@ -77,9 +86,8 @@ func (s *editScaffolder) Scaffold() error { // Check if the str is not empty, because when the file is already in desired format it will return empty string // because there is nothing to replace. if str != "" { - // false positive - // nolint:gosec - return ioutil.WriteFile(filename, []byte(str), 0644) + // TODO: instead of writing it directly, we should use the scaffolding machinery for consistency + return afero.WriteFile(s.fs, filename, []byte(str), 0644) } return nil diff --git a/pkg/plugins/golang/v3/scaffolds/init.go b/pkg/plugins/golang/v3/scaffolds/init.go index e46478d42ed..ae53ea95444 100644 --- a/pkg/plugins/golang/v3/scaffolds/init.go +++ b/pkg/plugins/golang/v3/scaffolds/init.go @@ -18,11 +18,13 @@ package scaffolds import ( "fmt" - "io/ioutil" "path/filepath" + "github.com/spf13/afero" + "sigs.k8s.io/kubebuilder/v3/pkg/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault" @@ -30,8 +32,6 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/hack" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" ) const ( @@ -45,17 +45,20 @@ const ( imageName = "controller:latest" ) -var _ cmdutil.Scaffolder = &initScaffolder{} +var _ plugins.Scaffolder = &initScaffolder{} type initScaffolder struct { config config.Config boilerplatePath string license string owner string + + // fs is the filesystem that will be used by the scaffolder + fs afero.Fs } // NewInitScaffolder returns a new Scaffolder for project initialization operations -func NewInitScaffolder(config config.Config, license, owner string) cmdutil.Scaffolder { +func NewInitScaffolder(config config.Config, license, owner string) plugins.Scaffolder { return &initScaffolder{ config: config, boilerplatePath: filepath.Join("hack", "boilerplate.go.txt"), @@ -64,39 +67,43 @@ func NewInitScaffolder(config config.Config, license, owner string) cmdutil.Scaf } } -func (s *initScaffolder) newUniverse(boilerplate string) *model.Universe { - return model.NewUniverse( - model.WithConfig(s.config), - model.WithBoilerplate(boilerplate), - ) +// InjectFS implements cmdutil.Scaffolder +func (s *initScaffolder) InjectFS(fs afero.Fs) { + s.fs = fs } -// Scaffold implements Scaffolder +// Scaffold implements cmdutil.Scaffolder func (s *initScaffolder) Scaffold() error { fmt.Println("Writing scaffold for you to edit...") - return s.scaffold() -} -// TODO: re-use universe created by s.newUniverse() if possible. -func (s *initScaffolder) scaffold() error { - bpFile := &hack.Boilerplate{} + // Initialize the machinery.Scaffold that will write the boilerplate file to disk + // The boilerplate file needs to be scaffolded as a separate step as it is going to + // be used by the rest of the files, even those scaffolded in this command call. + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + ) + + bpFile := &hack.Boilerplate{ + License: s.license, + Owner: s.owner, + } bpFile.Path = s.boilerplatePath - bpFile.License = s.license - bpFile.Owner = s.owner - if err := machinery.NewScaffold().Execute( - s.newUniverse(""), - bpFile, - ); err != nil { + if err := scaffold.Execute(bpFile); err != nil { return err } - boilerplate, err := ioutil.ReadFile(s.boilerplatePath) //nolint:gosec + boilerplate, err := afero.ReadFile(s.fs, s.boilerplatePath) if err != nil { return err } - return machinery.NewScaffold().Execute( - s.newUniverse(string(boilerplate)), + // Initialize the machinery.Scaffold that will write the files to disk + scaffold = machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + ) + + return scaffold.Execute( &rbac.Kustomization{}, &rbac.AuthProxyRole{}, &rbac.AuthProxyRoleBinding{}, diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/group.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/group.go index f8c1faa7e2b..8c93af689a0 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/group.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/group.go @@ -19,17 +19,17 @@ package api import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Group{} +var _ machinery.Template = &Group{} // Group scaffolds the file that defines the registration methods for a certain group and version type Group struct { - file.TemplateMixin - file.MultiGroupMixin - file.BoilerplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go index 4c253344db0..a76e3cb1391 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go @@ -20,18 +20,18 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Types{} +var _ machinery.Template = &Types{} // Types scaffolds the file that defines the schema for a CRD // nolint:maligned type Types struct { - file.TemplateMixin - file.MultiGroupMixin - file.BoilerplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin Force bool } @@ -55,9 +55,9 @@ func (f *Types) SetTemplateDefaults() error { f.TemplateBody = typesTemplate if f.Force { - f.IfExistsAction = file.Overwrite + f.IfExistsAction = machinery.OverwriteFile } else { - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error } return nil diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook.go index f3f9c6ccd76..f51ef49bda0 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook.go @@ -21,17 +21,17 @@ import ( "path/filepath" "strings" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Webhook{} +var _ machinery.Template = &Webhook{} // Webhook scaffolds the file that defines a webhook for a CRD or a builtin resource type Webhook struct { // nolint:maligned - file.TemplateMixin - file.MultiGroupMixin - file.BoilerplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin // Is the Group domain for the Resource replacing '.' with '-' QualifiedGroupWithDash string @@ -65,9 +65,9 @@ func (f *Webhook) SetTemplateDefaults() error { f.TemplateBody = webhookTemplate if f.Force { - f.IfExistsAction = file.Overwrite + f.IfExistsAction = machinery.OverwriteFile } else { - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error } f.QualifiedGroupWithDash = strings.Replace(f.Resource.QualifiedGroup(), ".", "-", -1) diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go index f708a8afbb8..7f30dd89f37 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go @@ -4,18 +4,18 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &WebhookSuite{} -var _ file.Inserter = &WebhookSuite{} +var _ machinery.Template = &WebhookSuite{} +var _ machinery.Inserter = &WebhookSuite{} // WebhookSuite scaffolds the file that sets up the webhook tests type WebhookSuite struct { //nolint:maligned - file.TemplateMixin - file.MultiGroupMixin - file.BoilerplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin // todo: currently is not possible to know if an API was or not scaffolded. We can fix it when #1826 be addressed WireResource bool @@ -40,9 +40,9 @@ func (f *WebhookSuite) SetTemplateDefaults() error { f.Path = f.Resource.Replacer().Replace(f.Path) f.TemplateBody = fmt.Sprintf(webhookTestSuiteTemplate, - file.NewMarkerFor(f.Path, importMarker), - file.NewMarkerFor(f.Path, addSchemeMarker), - file.NewMarkerFor(f.Path, addWebhookManagerMarker), + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), + machinery.NewMarkerFor(f.Path, addWebhookManagerMarker), "%s", "%d", ) @@ -66,11 +66,11 @@ const ( ) // GetMarkers implements file.Inserter -func (f *WebhookSuite) GetMarkers() []file.Marker { - return []file.Marker{ - file.NewMarkerFor(f.Path, importMarker), - file.NewMarkerFor(f.Path, addSchemeMarker), - file.NewMarkerFor(f.Path, addWebhookManagerMarker), +func (f *WebhookSuite) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), + machinery.NewMarkerFor(f.Path, addWebhookManagerMarker), } } @@ -88,8 +88,8 @@ Expect(err).NotTo(HaveOccurred()) ) // GetCodeFragments implements file.Inserter -func (f *WebhookSuite) GetCodeFragments() file.CodeFragmentsMap { - fragments := make(file.CodeFragmentsMap, 3) +func (f *WebhookSuite) GetCodeFragments() machinery.CodeFragmentsMap { + fragments := make(machinery.CodeFragmentsMap, 3) // Generate import code fragments imports := make([]string, 0) @@ -105,13 +105,13 @@ func (f *WebhookSuite) GetCodeFragments() file.CodeFragmentsMap { // Only store code fragments in the map if the slices are non-empty if len(addWebhookManager) != 0 { - fragments[file.NewMarkerFor(f.Path, addWebhookManagerMarker)] = addWebhookManager + fragments[machinery.NewMarkerFor(f.Path, addWebhookManagerMarker)] = addWebhookManager } if len(imports) != 0 { - fragments[file.NewMarkerFor(f.Path, importMarker)] = imports + fragments[machinery.NewMarkerFor(f.Path, importMarker)] = imports } if len(addScheme) != 0 { - fragments[file.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme + fragments[machinery.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme } return fragments diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/certificate.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/certificate.go index a654f82951f..76c2b186f65 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/certificate.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/certificate.go @@ -19,14 +19,14 @@ package certmanager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Certificate{} +var _ machinery.Template = &Certificate{} // Certificate scaffolds a file that defines the issuer CR and the certificate CR type Certificate struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/kustomization.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/kustomization.go index 4210f6f133b..522bdd9b67f 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/kustomization.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/kustomization.go @@ -19,14 +19,14 @@ package certmanager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Kustomization{} +var _ machinery.Template = &Kustomization{} // Kustomization scaffolds a file that defines the kustomization scheme for the certmanager folder type Kustomization struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go index 8ade67534fc..573a7e8f602 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go @@ -19,14 +19,14 @@ package certmanager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &KustomizeConfig{} +var _ machinery.Template = &KustomizeConfig{} // KustomizeConfig scaffolds a file that configures the kustomization for the certmanager folder type KustomizeConfig struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomization.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomization.go index 9a87af34668..23690588e9f 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomization.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomization.go @@ -20,16 +20,16 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Kustomization{} -var _ file.Inserter = &Kustomization{} +var _ machinery.Template = &Kustomization{} +var _ machinery.Inserter = &Kustomization{} // Kustomization scaffolds a file that defines the kustomization scheme for the crd folder type Kustomization struct { - file.TemplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.ResourceMixin } // SetTemplateDefaults implements file.Template @@ -40,9 +40,9 @@ func (f *Kustomization) SetTemplateDefaults() error { f.Path = f.Resource.Replacer().Replace(f.Path) f.TemplateBody = fmt.Sprintf(kustomizationTemplate, - file.NewMarkerFor(f.Path, resourceMarker), - file.NewMarkerFor(f.Path, webhookPatchMarker), - file.NewMarkerFor(f.Path, caInjectionPatchMarker), + machinery.NewMarkerFor(f.Path, resourceMarker), + machinery.NewMarkerFor(f.Path, webhookPatchMarker), + machinery.NewMarkerFor(f.Path, caInjectionPatchMarker), ) return nil @@ -55,11 +55,11 @@ const ( ) // GetMarkers implements file.Inserter -func (f *Kustomization) GetMarkers() []file.Marker { - return []file.Marker{ - file.NewMarkerFor(f.Path, resourceMarker), - file.NewMarkerFor(f.Path, webhookPatchMarker), - file.NewMarkerFor(f.Path, caInjectionPatchMarker), +func (f *Kustomization) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(f.Path, resourceMarker), + machinery.NewMarkerFor(f.Path, webhookPatchMarker), + machinery.NewMarkerFor(f.Path, caInjectionPatchMarker), } } @@ -73,8 +73,8 @@ const ( ) // GetCodeFragments implements file.Inserter -func (f *Kustomization) GetCodeFragments() file.CodeFragmentsMap { - fragments := make(file.CodeFragmentsMap, 3) +func (f *Kustomization) GetCodeFragments() machinery.CodeFragmentsMap { + fragments := make(machinery.CodeFragmentsMap, 3) // Generate resource code fragments res := make([]string, 0) @@ -90,13 +90,13 @@ func (f *Kustomization) GetCodeFragments() file.CodeFragmentsMap { // Only store code fragments in the map if the slices are non-empty if len(res) != 0 { - fragments[file.NewMarkerFor(f.Path, resourceMarker)] = res + fragments[machinery.NewMarkerFor(f.Path, resourceMarker)] = res } if len(webhookPatch) != 0 { - fragments[file.NewMarkerFor(f.Path, webhookPatchMarker)] = webhookPatch + fragments[machinery.NewMarkerFor(f.Path, webhookPatchMarker)] = webhookPatch } if len(caInjectionPatch) != 0 { - fragments[file.NewMarkerFor(f.Path, caInjectionPatchMarker)] = caInjectionPatch + fragments[machinery.NewMarkerFor(f.Path, caInjectionPatchMarker)] = caInjectionPatch } return fragments diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomizeconfig.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomizeconfig.go index 7b56a21c9df..428bfde8b88 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomizeconfig.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomizeconfig.go @@ -19,15 +19,15 @@ package crd import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &KustomizeConfig{} +var _ machinery.Template = &KustomizeConfig{} // KustomizeConfig scaffolds a file that configures the kustomization for the crd folder type KustomizeConfig struct { - file.TemplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.ResourceMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go index c954670d1db..cc688e50f63 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go @@ -19,15 +19,15 @@ package patches import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &EnableCAInjectionPatch{} +var _ machinery.Template = &EnableCAInjectionPatch{} // EnableCAInjectionPatch scaffolds a file that defines the patch that injects CA into the CRD type EnableCAInjectionPatch struct { - file.TemplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.ResourceMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go index 7cc1da1d65e..1bf0e7ac071 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go @@ -19,15 +19,15 @@ package patches import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &EnableWebhookPatch{} +var _ machinery.Template = &EnableWebhookPatch{} // EnableWebhookPatch scaffolds a file that defines the patch that enables conversion webhook for the CRD type EnableWebhookPatch struct { - file.TemplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.ResourceMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go index a3ba80da2b9..d93780f7dfa 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go @@ -19,15 +19,15 @@ package kdefault import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &WebhookCAInjectionPatch{} +var _ machinery.Template = &WebhookCAInjectionPatch{} // WebhookCAInjectionPatch scaffolds a file that defines the patch that adds annotation to webhooks type WebhookCAInjectionPatch struct { - file.TemplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.ResourceMixin } // SetTemplateDefaults implements file.Template @@ -39,7 +39,7 @@ func (f *WebhookCAInjectionPatch) SetTemplateDefaults() error { f.TemplateBody = injectCAPatchTemplate // If file exists (ex. because a webhook was already created), skip creation. - f.IfExistsAction = file.Skip + f.IfExistsAction = machinery.SkipFile return nil } diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/kustomization.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/kustomization.go index c7aea4259fb..b6860c307d0 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/kustomization.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/kustomization.go @@ -19,16 +19,16 @@ package kdefault import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Kustomization{} +var _ machinery.Template = &Kustomization{} // Kustomization scaffolds a file that defines the kustomization scheme for the default overlay folder type Kustomization struct { - file.TemplateMixin - file.ProjectNameMixin - file.ComponentConfigMixin + machinery.TemplateMixin + machinery.ProjectNameMixin + machinery.ComponentConfigMixin } // SetTemplateDefaults implements file.Template @@ -39,7 +39,7 @@ func (f *Kustomization) SetTemplateDefaults() error { f.TemplateBody = kustomizeTemplate - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error return nil } diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go index 8cf97588bc9..c47ab74426f 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go @@ -19,15 +19,15 @@ package kdefault import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &ManagerAuthProxyPatch{} +var _ machinery.Template = &ManagerAuthProxyPatch{} // ManagerAuthProxyPatch scaffolds a file that defines the patch that enables prometheus metrics for the manager type ManagerAuthProxyPatch struct { - file.TemplateMixin - file.ComponentConfigMixin + machinery.TemplateMixin + machinery.ComponentConfigMixin } // SetTemplateDefaults implements file.Template @@ -38,7 +38,7 @@ func (f *ManagerAuthProxyPatch) SetTemplateDefaults() error { f.TemplateBody = kustomizeAuthProxyPatchTemplate - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error return nil } diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_config_patch.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_config_patch.go index 8b32276fe7b..fb620573bbe 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_config_patch.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_config_patch.go @@ -19,14 +19,14 @@ package kdefault import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &ManagerConfigPatch{} +var _ machinery.Template = &ManagerConfigPatch{} // ManagerConfigPatch scaffolds a ManagerConfigPatch for a Resource type ManagerConfigPatch struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements input.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go index 3d86dd4f091..7f993dbd31c 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go @@ -19,14 +19,14 @@ package kdefault import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &ManagerWebhookPatch{} +var _ machinery.Template = &ManagerWebhookPatch{} // ManagerWebhookPatch scaffolds a file that defines the patch that enables webhooks on the manager type ManagerWebhookPatch struct { - file.TemplateMixin + machinery.TemplateMixin Force bool } @@ -40,10 +40,10 @@ func (f *ManagerWebhookPatch) SetTemplateDefaults() error { f.TemplateBody = managerWebhookPatchTemplate if f.Force { - f.IfExistsAction = file.Overwrite + f.IfExistsAction = machinery.OverwriteFile } else { // If file exists (ex. because a webhook was already created), skip creation. - f.IfExistsAction = file.Skip + f.IfExistsAction = machinery.SkipFile } return nil diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/config.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/config.go index 96663a7a6c9..eb972d5c2b3 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/config.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/config.go @@ -19,15 +19,15 @@ package manager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Config{} +var _ machinery.Template = &Config{} // Config scaffolds a file that defines the namespace and the manager deployment type Config struct { - file.TemplateMixin - file.ComponentConfigMixin + machinery.TemplateMixin + machinery.ComponentConfigMixin // Image is controller manager image name Image string diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/controller_manager_config.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/controller_manager_config.go index ea18f2145a0..fa977d90ecb 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/controller_manager_config.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/controller_manager_config.go @@ -19,16 +19,16 @@ package manager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &ControllerManagerConfig{} +var _ machinery.Template = &ControllerManagerConfig{} // ControllerManagerConfig scaffolds the config file in config/manager folder. type ControllerManagerConfig struct { - file.TemplateMixin - file.DomainMixin - file.RepositoryMixin + machinery.TemplateMixin + machinery.DomainMixin + machinery.RepositoryMixin } // SetTemplateDefaults implements input.Template @@ -39,7 +39,7 @@ func (f *ControllerManagerConfig) SetTemplateDefaults() error { f.TemplateBody = controllerManagerConfigTemplate - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error return nil } diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/kustomization.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/kustomization.go index 5b3f307acfb..eec807da3d1 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/kustomization.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/kustomization.go @@ -19,14 +19,14 @@ package manager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Kustomization{} +var _ machinery.Template = &Kustomization{} // Kustomization scaffolds a file that defines the kustomization scheme for the manager folder type Kustomization struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template @@ -37,7 +37,7 @@ func (f *Kustomization) SetTemplateDefaults() error { f.TemplateBody = kustomizeManagerTemplate - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error return nil } diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus/kustomization.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus/kustomization.go index e2cd771253a..76bf6e1c5e1 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus/kustomization.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus/kustomization.go @@ -19,14 +19,14 @@ package prometheus import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Kustomization{} +var _ machinery.Template = &Kustomization{} // Kustomization scaffolds a file that defines the kustomization scheme for the prometheus folder type Kustomization struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus/monitor.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus/monitor.go index 4ee7d32c1c2..41870f6850a 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus/monitor.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus/monitor.go @@ -19,14 +19,14 @@ package prometheus import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Monitor{} +var _ machinery.Template = &Monitor{} // Monitor scaffolds a file that defines the prometheus service monitor type Monitor struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go index d7bfee31882..e41eeceb55e 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go @@ -19,14 +19,14 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &AuthProxyClientRole{} +var _ machinery.Template = &AuthProxyClientRole{} // AuthProxyClientRole scaffolds a file that defines the role for the metrics reader type AuthProxyClientRole struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_role.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_role.go index f5900ec2b8e..0f359d78e4c 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_role.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_role.go @@ -19,14 +19,14 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &AuthProxyRole{} +var _ machinery.Template = &AuthProxyRole{} // AuthProxyRole scaffolds a file that defines the role for the auth proxy type AuthProxyRole struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go index 3834de9bce4..b858591fae6 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go @@ -19,14 +19,14 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &AuthProxyRoleBinding{} +var _ machinery.Template = &AuthProxyRoleBinding{} // AuthProxyRoleBinding scaffolds a file that defines the role binding for the auth proxy type AuthProxyRoleBinding struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_service.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_service.go index ffd9cd2ab19..f9c7249c829 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_service.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_service.go @@ -19,14 +19,14 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &AuthProxyService{} +var _ machinery.Template = &AuthProxyService{} // AuthProxyService scaffolds a file that defines the service for the auth proxy type AuthProxyService struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_editor_role.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_editor_role.go index a099b595e9b..a8832864277 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_editor_role.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_editor_role.go @@ -19,15 +19,15 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &CRDEditorRole{} +var _ machinery.Template = &CRDEditorRole{} // CRDEditorRole scaffolds a file that defines the role that allows to edit plurals type CRDEditorRole struct { - file.TemplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.ResourceMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_viewer_role.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_viewer_role.go index 0b3311650b7..d83c3295f53 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_viewer_role.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_viewer_role.go @@ -19,15 +19,15 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &CRDViewerRole{} +var _ machinery.Template = &CRDViewerRole{} // CRDViewerRole scaffolds a file that defines the role that allows to view plurals type CRDViewerRole struct { - file.TemplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.ResourceMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/kustomization.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/kustomization.go index 0966bd6d5d9..a4d285d508e 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/kustomization.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/kustomization.go @@ -19,14 +19,14 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Kustomization{} +var _ machinery.Template = &Kustomization{} // Kustomization scaffolds a file that defines the kustomization scheme for the rbac folder type Kustomization struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template @@ -37,7 +37,7 @@ func (f *Kustomization) SetTemplateDefaults() error { f.TemplateBody = kustomizeRBACTemplate - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error return nil } diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/leader_election_role.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/leader_election_role.go index 3f85432601f..1008bf2c387 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/leader_election_role.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/leader_election_role.go @@ -19,14 +19,14 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &LeaderElectionRole{} +var _ machinery.Template = &LeaderElectionRole{} // LeaderElectionRole scaffolds a file that defines the role that allows leader election type LeaderElectionRole struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go index 8c67aac8a54..0d9401a9ba3 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go @@ -19,14 +19,14 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &LeaderElectionRoleBinding{} +var _ machinery.Template = &LeaderElectionRoleBinding{} // LeaderElectionRoleBinding scaffolds a file that defines the role binding that allows leader election type LeaderElectionRoleBinding struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/role_binding.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/role_binding.go index 40f1bd783e1..659dc5a4316 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/role_binding.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/role_binding.go @@ -19,14 +19,14 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &RoleBinding{} +var _ machinery.Template = &RoleBinding{} // RoleBinding scaffolds a file that defines the role binding for the manager type RoleBinding struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/samples/crd_sample.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/samples/crd_sample.go index ab68ba16e4c..f813a2353b7 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/samples/crd_sample.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/samples/crd_sample.go @@ -19,15 +19,15 @@ package samples import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &CRDSample{} +var _ machinery.Template = &CRDSample{} // CRDSample scaffolds a file that defines a sample manifest for the CRD type CRDSample struct { - file.TemplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.ResourceMixin Force bool } @@ -40,9 +40,9 @@ func (f *CRDSample) SetTemplateDefaults() error { f.Path = f.Resource.Replacer().Replace(f.Path) if f.Force { - f.IfExistsAction = file.Overwrite + f.IfExistsAction = machinery.OverwriteFile } else { - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error } f.TemplateBody = crdSampleTemplate diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomization.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomization.go index 74546ab10ae..3f55f70a12f 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomization.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomization.go @@ -19,15 +19,15 @@ package webhook import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Kustomization{} +var _ machinery.Template = &Kustomization{} // Kustomization scaffolds a file that defines the kustomization scheme for the webhook folder type Kustomization struct { - file.TemplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.ResourceMixin Force bool } @@ -41,10 +41,10 @@ func (f *Kustomization) SetTemplateDefaults() error { f.TemplateBody = kustomizeWebhookTemplate if f.Force { - f.IfExistsAction = file.Overwrite + f.IfExistsAction = machinery.OverwriteFile } else { // If file exists (ex. because a webhook was already created), skip creation. - f.IfExistsAction = file.Skip + f.IfExistsAction = machinery.SkipFile } return nil diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomizeconfig.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomizeconfig.go index a719ae63c46..524f11e71c1 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomizeconfig.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomizeconfig.go @@ -19,14 +19,14 @@ package webhook import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &KustomizeConfig{} +var _ machinery.Template = &KustomizeConfig{} // KustomizeConfig scaffolds a file that configures the kustomization for the webhook folder type KustomizeConfig struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template @@ -38,7 +38,7 @@ func (f *KustomizeConfig) SetTemplateDefaults() error { f.TemplateBody = kustomizeConfigWebhookTemplate // If file exists (ex. because a webhook was already created), skip creation. - f.IfExistsAction = file.Skip + f.IfExistsAction = machinery.SkipFile return nil } diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/service.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/service.go index f05e6bc719a..a7052dbb3bc 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/service.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/service.go @@ -19,14 +19,14 @@ package webhook import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Service{} +var _ machinery.Template = &Service{} // Service scaffolds a file that defines the webhook service type Service struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template @@ -38,7 +38,7 @@ func (f *Service) SetTemplateDefaults() error { f.TemplateBody = serviceTemplate // If file exists (ex. because a webhook was already created), skip creation. - f.IfExistsAction = file.Skip + f.IfExistsAction = machinery.SkipFile return nil } diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go index 779edb04b45..32d64bcf006 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go @@ -20,18 +20,18 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Controller{} +var _ machinery.Template = &Controller{} // Controller scaffolds the file that defines the controller for a CRD or a builtin resource // nolint:maligned type Controller struct { - file.TemplateMixin - file.MultiGroupMixin - file.BoilerplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin ControllerRuntimeVersion string @@ -53,9 +53,9 @@ func (f *Controller) SetTemplateDefaults() error { f.TemplateBody = controllerTemplate if f.Force { - f.IfExistsAction = file.Overwrite + f.IfExistsAction = machinery.OverwriteFile } else { - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error } return nil diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go index d6e6ac95af5..1dbb649a40c 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go @@ -20,19 +20,19 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &SuiteTest{} -var _ file.Inserter = &SuiteTest{} +var _ machinery.Template = &SuiteTest{} +var _ machinery.Inserter = &SuiteTest{} // SuiteTest scaffolds the file that sets up the controller tests // nolint:maligned type SuiteTest struct { - file.TemplateMixin - file.MultiGroupMixin - file.BoilerplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin // CRDDirectoryRelativePath define the Path for the CRD CRDDirectoryRelativePath string @@ -52,8 +52,8 @@ func (f *SuiteTest) SetTemplateDefaults() error { f.Path = f.Resource.Replacer().Replace(f.Path) f.TemplateBody = fmt.Sprintf(controllerSuiteTestTemplate, - file.NewMarkerFor(f.Path, importMarker), - file.NewMarkerFor(f.Path, addSchemeMarker), + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), ) // If is multigroup the path needs to be ../../ since it has @@ -64,7 +64,7 @@ func (f *SuiteTest) SetTemplateDefaults() error { } if f.Force { - f.IfExistsAction = file.Overwrite + f.IfExistsAction = machinery.OverwriteFile } return nil @@ -76,10 +76,10 @@ const ( ) // GetMarkers implements file.Inserter -func (f *SuiteTest) GetMarkers() []file.Marker { - return []file.Marker{ - file.NewMarkerFor(f.Path, importMarker), - file.NewMarkerFor(f.Path, addSchemeMarker), +func (f *SuiteTest) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), } } @@ -93,8 +93,8 @@ Expect(err).NotTo(HaveOccurred()) ) // GetCodeFragments implements file.Inserter -func (f *SuiteTest) GetCodeFragments() file.CodeFragmentsMap { - fragments := make(file.CodeFragmentsMap, 2) +func (f *SuiteTest) GetCodeFragments() machinery.CodeFragmentsMap { + fragments := make(machinery.CodeFragmentsMap, 2) // Generate import code fragments imports := make([]string, 0) @@ -110,10 +110,10 @@ func (f *SuiteTest) GetCodeFragments() file.CodeFragmentsMap { // Only store code fragments in the map if the slices are non-empty if len(imports) != 0 { - fragments[file.NewMarkerFor(f.Path, importMarker)] = imports + fragments[machinery.NewMarkerFor(f.Path, importMarker)] = imports } if len(addScheme) != 0 { - fragments[file.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme + fragments[machinery.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme } return fragments diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerfile.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerfile.go index 359a0a2ebc5..7794ab4e08c 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerfile.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerfile.go @@ -17,14 +17,14 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Dockerfile{} +var _ machinery.Template = &Dockerfile{} // Dockerfile scaffolds a file that defines the containerized build process type Dockerfile struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerignore.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerignore.go index 7aa0150303d..2051adc956c 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerignore.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerignore.go @@ -17,14 +17,14 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &DockerIgnore{} +var _ machinery.Template = &DockerIgnore{} // DockerIgnore scaffolds a file that defines which files should be ignored by the containerized build process type DockerIgnore struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/gitignore.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/gitignore.go index bbf1e92bf53..2b5ef060f98 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/gitignore.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/gitignore.go @@ -17,14 +17,14 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &GitIgnore{} +var _ machinery.Template = &GitIgnore{} // GitIgnore scaffolds a file that defines which files should be ignored by git type GitIgnore struct { - file.TemplateMixin + machinery.TemplateMixin } // SetTemplateDefaults implements file.Template diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/gomod.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/gomod.go index fdb3ce26764..e79e17bf47a 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/gomod.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/gomod.go @@ -17,15 +17,15 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &GoMod{} +var _ machinery.Template = &GoMod{} // GoMod scaffolds a file that defines the project dependencies type GoMod struct { - file.TemplateMixin - file.RepositoryMixin + machinery.TemplateMixin + machinery.RepositoryMixin ControllerRuntimeVersion string } @@ -38,7 +38,7 @@ func (f *GoMod) SetTemplateDefaults() error { f.TemplateBody = goModTemplate - f.IfExistsAction = file.Overwrite + f.IfExistsAction = machinery.OverwriteFile return nil } diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/hack/boilerplate.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/hack/boilerplate.go index e8a21cbe6ce..3efbe592a54 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/hack/boilerplate.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/hack/boilerplate.go @@ -21,15 +21,18 @@ import ( "path/filepath" "time" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Boilerplate{} +// DefaultBoilerplatePath is the default path to the boilerplate file +var DefaultBoilerplatePath = filepath.Join("hack", "boilerplate.go.txt") + +var _ machinery.Template = &Boilerplate{} // Boilerplate scaffolds a file that defines the common header for the rest of the files type Boilerplate struct { - file.TemplateMixin - file.BoilerplateMixin + machinery.TemplateMixin + machinery.BoilerplateMixin // License is the License type to write License string @@ -62,7 +65,7 @@ func (f Boilerplate) Validate() error { // SetTemplateDefaults implements file.Template func (f *Boilerplate) SetTemplateDefaults() error { if f.Path == "" { - f.Path = filepath.Join("hack", "boilerplate.go.txt") + f.Path = DefaultBoilerplatePath } if f.License == "" { diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go index 65863974cb7..df7abb4751f 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go @@ -20,20 +20,20 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) const defaultMainPath = "main.go" -var _ file.Template = &Main{} +var _ machinery.Template = &Main{} // Main scaffolds a file that defines the controller manager entry point type Main struct { - file.TemplateMixin - file.BoilerplateMixin - file.DomainMixin - file.RepositoryMixin - file.ComponentConfigMixin + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.DomainMixin + machinery.RepositoryMixin + machinery.ComponentConfigMixin } // SetTemplateDefaults implements file.Template @@ -43,21 +43,21 @@ func (f *Main) SetTemplateDefaults() error { } f.TemplateBody = fmt.Sprintf(mainTemplate, - file.NewMarkerFor(f.Path, importMarker), - file.NewMarkerFor(f.Path, addSchemeMarker), - file.NewMarkerFor(f.Path, setupMarker), + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), + machinery.NewMarkerFor(f.Path, setupMarker), ) return nil } -var _ file.Inserter = &MainUpdater{} +var _ machinery.Inserter = &MainUpdater{} // MainUpdater updates main.go to run Controllers type MainUpdater struct { //nolint:maligned - file.RepositoryMixin - file.MultiGroupMixin - file.ResourceMixin + machinery.RepositoryMixin + machinery.MultiGroupMixin + machinery.ResourceMixin // Flags to indicate which parts need to be included when updating the file WireResource, WireController, WireWebhook bool @@ -69,8 +69,8 @@ func (*MainUpdater) GetPath() string { } // GetIfExistsAction implements file.Builder -func (*MainUpdater) GetIfExistsAction() file.IfExistsAction { - return file.Overwrite +func (*MainUpdater) GetIfExistsAction() machinery.IfExistsAction { + return machinery.OverwriteFile } const ( @@ -80,11 +80,11 @@ const ( ) // GetMarkers implements file.Inserter -func (f *MainUpdater) GetMarkers() []file.Marker { - return []file.Marker{ - file.NewMarkerFor(defaultMainPath, importMarker), - file.NewMarkerFor(defaultMainPath, addSchemeMarker), - file.NewMarkerFor(defaultMainPath, setupMarker), +func (f *MainUpdater) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(defaultMainPath, importMarker), + machinery.NewMarkerFor(defaultMainPath, addSchemeMarker), + machinery.NewMarkerFor(defaultMainPath, setupMarker), } } @@ -123,8 +123,8 @@ const ( ) // GetCodeFragments implements file.Inserter -func (f *MainUpdater) GetCodeFragments() file.CodeFragmentsMap { - fragments := make(file.CodeFragmentsMap, 3) +func (f *MainUpdater) GetCodeFragments() machinery.CodeFragmentsMap { + fragments := make(machinery.CodeFragmentsMap, 3) // If resource is not being provided we are creating the file, not updating it if f.Resource == nil { @@ -170,13 +170,13 @@ func (f *MainUpdater) GetCodeFragments() file.CodeFragmentsMap { // Only store code fragments in the map if the slices are non-empty if len(imports) != 0 { - fragments[file.NewMarkerFor(defaultMainPath, importMarker)] = imports + fragments[machinery.NewMarkerFor(defaultMainPath, importMarker)] = imports } if len(addScheme) != 0 { - fragments[file.NewMarkerFor(defaultMainPath, addSchemeMarker)] = addScheme + fragments[machinery.NewMarkerFor(defaultMainPath, addSchemeMarker)] = addScheme } if len(setup) != 0 { - fragments[file.NewMarkerFor(defaultMainPath, setupMarker)] = setup + fragments[machinery.NewMarkerFor(defaultMainPath, setupMarker)] = setup } return fragments diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go index 09b983942fa..787c1ef34f2 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go @@ -17,15 +17,15 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Makefile{} +var _ machinery.Template = &Makefile{} // Makefile scaffolds a file that defines project management CLI commands type Makefile struct { - file.TemplateMixin - file.ComponentConfigMixin + machinery.TemplateMixin + machinery.ComponentConfigMixin // Image is controller manager image name Image string @@ -47,7 +47,7 @@ func (f *Makefile) SetTemplateDefaults() error { f.TemplateBody = makefileTemplate - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error if f.Image == "" { f.Image = "controller:latest" diff --git a/pkg/plugins/golang/v3/scaffolds/webhook.go b/pkg/plugins/golang/v3/scaffolds/webhook.go index b195ee7eef7..3403411628b 100644 --- a/pkg/plugins/golang/v3/scaffolds/webhook.go +++ b/pkg/plugins/golang/v3/scaffolds/webhook.go @@ -19,58 +19,63 @@ package scaffolds import ( "fmt" + "github.com/spf13/afero" + "sigs.k8s.io/kubebuilder/v3/pkg/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/api" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/hack" ) -var _ cmdutil.Scaffolder = &webhookScaffolder{} +var _ plugins.Scaffolder = &webhookScaffolder{} type webhookScaffolder struct { - config config.Config - boilerplate string - resource resource.Resource + config config.Config + resource resource.Resource + + // fs is the filesystem that will be used by the scaffolder + fs afero.Fs // force indicates whether to scaffold controller files even if it exists or not force bool } // NewWebhookScaffolder returns a new Scaffolder for v2 webhook creation operations -func NewWebhookScaffolder( - config config.Config, - boilerplate string, - resource resource.Resource, - force bool, -) cmdutil.Scaffolder { +func NewWebhookScaffolder(config config.Config, resource resource.Resource, force bool) plugins.Scaffolder { return &webhookScaffolder{ - config: config, - boilerplate: boilerplate, - resource: resource, - force: force, + config: config, + resource: resource, + force: force, } } -// Scaffold implements Scaffolder +// InjectFS implements cmdutil.Scaffolder +func (s *webhookScaffolder) InjectFS(fs afero.Fs) { + s.fs = fs +} + +// Scaffold implements cmdutil.Scaffolder func (s *webhookScaffolder) Scaffold() error { fmt.Println("Writing scaffold for you to edit...") - return s.scaffold() -} -func (s *webhookScaffolder) newUniverse() *model.Universe { - return model.NewUniverse( - model.WithConfig(s.config), - model.WithBoilerplate(s.boilerplate), - model.WithResource(&s.resource), + // Load the boilerplate + boilerplate, err := afero.ReadFile(s.fs, hack.DefaultBoilerplatePath) + if err != nil { + return fmt.Errorf("error scaffolding webhook: unable to load boilerplate: %w", err) + } + + // Initialize the machinery.Scaffold that will write the files to disk + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + machinery.WithResource(&s.resource), ) -} -func (s *webhookScaffolder) scaffold() error { // Keep track of these values before the update doDefaulting := s.resource.HasDefaultingWebhook() doValidation := s.resource.HasValidationWebhook() @@ -80,8 +85,7 @@ func (s *webhookScaffolder) scaffold() error { return fmt.Errorf("error updating resource: %w", err) } - if err := machinery.NewScaffold().Execute( - s.newUniverse(), + if err := scaffold.Execute( &api.Webhook{Force: s.force}, &templates.MainUpdater{WireWebhook: true}, &kdefault.WebhookCAInjectionPatch{}, @@ -100,8 +104,7 @@ You need to implement the conversion.Hub and conversion.Convertible interfaces f // TODO: Add test suite for conversion webhook after #1664 has been merged & conversion tests supported in envtest. if doDefaulting || doValidation { - if err := machinery.NewScaffold().Execute( - s.newUniverse(), + if err := scaffold.Execute( &api.WebhookSuite{}, ); err != nil { return err diff --git a/pkg/plugins/golang/v3/webhook.go b/pkg/plugins/golang/v3/webhook.go index 7c81b017fae..781b288527c 100644 --- a/pkg/plugins/golang/v3/webhook.go +++ b/pkg/plugins/golang/v3/webhook.go @@ -18,9 +18,8 @@ package v3 import ( "fmt" - "io/ioutil" - "path/filepath" + "github.com/spf13/afero" "github.com/spf13/pflag" "sigs.k8s.io/kubebuilder/v3/pkg/config" @@ -28,12 +27,13 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/plugin" goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" ) // defaultWebhookVersion is the default mutating/validating webhook config API version to scaffold. const defaultWebhookVersion = "v1" +var _ plugin.CreateWebhookSubcommand = &createWebhookSubcommand{} + type createWebhookSubcommand struct { config config.Config // For help text. @@ -41,39 +41,30 @@ type createWebhookSubcommand struct { options *goPlugin.Options - resource resource.Resource + resource *resource.Resource // force indicates that the resource should be created even if it already exists force bool } -var ( - _ plugin.CreateWebhookSubcommand = &createWebhookSubcommand{} - _ cmdutil.RunOptions = &createWebhookSubcommand{} -) +func (p *createWebhookSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + p.commandName = cliMeta.CommandName -func (p *createWebhookSubcommand) UpdateContext(ctx *plugin.Context) { - ctx.Description = `Scaffold a webhook for an API resource. You can choose to scaffold defaulting, + subcmdMeta.Description = `Scaffold a webhook for an API resource. You can choose to scaffold defaulting, validating and (or) conversion webhooks. ` - ctx.Examples = fmt.Sprintf(` # Create defaulting and validating webhooks for CRD of group ship, version v1beta1 + subcmdMeta.Examples = fmt.Sprintf(` # Create defaulting and validating webhooks for CRD of group ship, version v1beta1 # and kind Frigate. - %s create webhook --group ship --version v1beta1 --kind Frigate --defaulting --programmatic-validation + %[1]s create webhook --group ship --version v1beta1 --kind Frigate --defaulting --programmatic-validation # Create conversion webhook for CRD of group ship, version v1beta1 and kind Frigate. - %s create webhook --group ship --version v1beta1 --kind Frigate --conversion -`, - ctx.CommandName, ctx.CommandName) - - p.commandName = ctx.CommandName + %[1]s create webhook --group ship --version v1beta1 --kind Frigate --conversion +`, cliMeta.CommandName) } func (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) { p.options = &goPlugin.Options{} - fs.StringVar(&p.options.Group, "group", "", "resource Group") - p.options.Domain = p.config.GetDomain() - fs.StringVar(&p.options.Version, "version", "", "resource Version") - fs.StringVar(&p.options.Kind, "kind", "", "resource Kind") + fs.StringVar(&p.options.Plural, "plural", "", "resource irregular plural form") fs.StringVar(&p.options.WebhookVersion, "webhook-version", defaultWebhookVersion, @@ -89,21 +80,16 @@ func (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) { "attempt to create resource even if it already exists") } -func (p *createWebhookSubcommand) InjectConfig(c config.Config) { +func (p *createWebhookSubcommand) InjectConfig(c config.Config) error { p.config = c -} - -func (p *createWebhookSubcommand) Run() error { - // Create the resource from the options - p.resource = p.options.NewResource(p.config) - return cmdutil.Run(p) + return nil } -func (p *createWebhookSubcommand) Validate() error { - if err := p.options.Validate(); err != nil { - return err - } +func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error { + p.resource = res + + p.options.UpdateResource(p.resource, p.config) if err := p.resource.Validate(); err != nil { return err @@ -129,16 +115,8 @@ func (p *createWebhookSubcommand) Validate() error { return nil } -func (p *createWebhookSubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { - // Load the boilerplate - bp, err := ioutil.ReadFile(filepath.Join("hack", "boilerplate.go.txt")) // nolint:gosec - if err != nil { - return nil, fmt.Errorf("unable to load boilerplate: %v", err) - } - - return scaffolds.NewWebhookScaffolder(p.config, string(bp), p.resource, p.force), nil -} - -func (p *createWebhookSubcommand) PostScaffold() error { - return nil +func (p *createWebhookSubcommand) Scaffold(fs afero.Fs) error { + scaffolder := scaffolds.NewWebhookScaffolder(p.config, *p.resource, p.force) + scaffolder.InjectFS(fs) + return scaffolder.Scaffold() } diff --git a/pkg/plugins/internal/cmdutil/cmdutil.go b/pkg/plugins/internal/cmdutil/cmdutil.go deleted file mode 100644 index aa3e7f4a9e9..00000000000 --- a/pkg/plugins/internal/cmdutil/cmdutil.go +++ /dev/null @@ -1,60 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cmdutil - -// Scaffolder interface creates files to set up a controller manager -type Scaffolder interface { - // Scaffold performs the scaffolding - Scaffold() error -} - -// RunOptions represent the types used to implement the different commands -type RunOptions interface { - // - Step 1: verify that the command can be run (e.g., go version, project version, arguments, ...) - Validate() error - // - Step 2: create the Scaffolder instance - GetScaffolder() (Scaffolder, error) - // - Step 3: call the Scaffold method of the Scaffolder instance. Doesn't need any method - // - Step 4: finish the command execution - PostScaffold() error -} - -// Run executes a command -func Run(options RunOptions) error { - // Step 1: validate - if err := options.Validate(); err != nil { - return err - } - - // Step 2: get scaffolder - scaffolder, err := options.GetScaffolder() - if err != nil { - return err - } - // Step 3: scaffold - if scaffolder != nil { - if err := scaffolder.Scaffold(); err != nil { - return err - } - } - // Step 4: finish - if err := options.PostScaffold(); err != nil { - return err - } - - return nil -} diff --git a/pkg/plugins/internal/machinery/errors.go b/pkg/plugins/internal/machinery/errors.go deleted file mode 100644 index faba57a1d05..00000000000 --- a/pkg/plugins/internal/machinery/errors.go +++ /dev/null @@ -1,74 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -import ( - "errors" - "fmt" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -// This file contains the errors returned by the scaffolding machinery -// They are not exported as they should not be created outside of this package -// Exported functions are provided to check which kind of error was returned - -// fileAlreadyExistsError is returned if the file is expected not to exist but it does -type fileAlreadyExistsError struct { - path string -} - -// Error implements error interface -func (e fileAlreadyExistsError) Error() string { - return fmt.Sprintf("failed to create %s: file already exists", e.path) -} - -// IsFileAlreadyExistsError checks if the returned error is because the file already existed when expected not to -func IsFileAlreadyExistsError(err error) bool { - return errors.As(err, &fileAlreadyExistsError{}) -} - -// modelAlreadyExistsError is returned if the file is expected not to exist but a previous model does -type modelAlreadyExistsError struct { - path string -} - -// Error implements error interface -func (e modelAlreadyExistsError) Error() string { - return fmt.Sprintf("failed to create %s: model already exists", e.path) -} - -// IsModelAlreadyExistsError checks if the returned error is because the model already existed when expected not to -func IsModelAlreadyExistsError(err error) bool { - return errors.As(err, &modelAlreadyExistsError{}) -} - -// unknownIfExistsActionError is returned if the if-exists-action is unknown -type unknownIfExistsActionError struct { - path string - ifExistsAction file.IfExistsAction -} - -// Error implements error interface -func (e unknownIfExistsActionError) Error() string { - return fmt.Sprintf("unknown behavior if file exists (%d) for %s", e.ifExistsAction, e.path) -} - -// IsUnknownIfExistsActionError checks if the returned error is because the if-exists-action is unknown -func IsUnknownIfExistsActionError(err error) bool { - return errors.As(err, &unknownIfExistsActionError{}) -} diff --git a/pkg/plugins/internal/machinery/errors_test.go b/pkg/plugins/internal/machinery/errors_test.go deleted file mode 100644 index adcf32b4f70..00000000000 --- a/pkg/plugins/internal/machinery/errors_test.go +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -import ( - "errors" - "path/filepath" - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" -) - -func TestErrors(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Error suite") -} - -var _ = Describe("Errors", func() { - var ( - path = filepath.Join("path", "to", "file") - err = errors.New("test error") - fileAlreadyExistsErr = fileAlreadyExistsError{path} - modelAlreadyExistsErr = modelAlreadyExistsError{path} - unknownIfExistsActionErr = unknownIfExistsActionError{path, -1} - ) - - DescribeTable("IsXxxxError should return true for themselves and false for the rest", - func(f func(error) bool, itself error, rest ...error) { - Expect(f(itself)).To(BeTrue()) - for _, err := range rest { - Expect(f(err)).To(BeFalse()) - } - }, - Entry("file exists", IsFileAlreadyExistsError, fileAlreadyExistsErr, - err, modelAlreadyExistsErr, unknownIfExistsActionErr), - Entry("model exists", IsModelAlreadyExistsError, modelAlreadyExistsErr, - err, fileAlreadyExistsErr, unknownIfExistsActionErr), - Entry("unknown if exists action", IsUnknownIfExistsActionError, unknownIfExistsActionErr, - err, fileAlreadyExistsErr, modelAlreadyExistsErr), - ) - - DescribeTable("should contain the wrapped error and error message", - func(err error) { - Expect(err).To(MatchError(err)) - Expect(err.Error()).To(ContainSubstring(err.Error())) - }, - ) - - // NOTE: the following test increases coverage - It("should print a descriptive error message", func() { - Expect(fileAlreadyExistsErr.Error()).To(ContainSubstring("file already exists")) - Expect(modelAlreadyExistsErr.Error()).To(ContainSubstring("model already exists")) - Expect(unknownIfExistsActionErr.Error()).To(ContainSubstring("unknown behavior if file exists")) - }) -}) diff --git a/pkg/plugins/scaffolder.go b/pkg/plugins/scaffolder.go new file mode 100644 index 00000000000..f487d0caa5a --- /dev/null +++ b/pkg/plugins/scaffolder.go @@ -0,0 +1,28 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugins + +import ( + "github.com/spf13/afero" +) + +// Scaffolder interface creates files to set up a controller manager +type Scaffolder interface { + InjectFS(afero.Fs) + // Scaffold performs the scaffolding + Scaffold() error +} diff --git a/plugins/README.md b/plugins/README.md deleted file mode 100644 index 4cab5423bc2..00000000000 --- a/plugins/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Kubebuilder plugins - -**Status: Experimental** - -We are developing a plugin system to kubebuilder, so that we can generate -operators that follow other patterns. - -While plugins remain experimental, you must pass the `KUBEBUILDER_ENABLE_PLUGINS=1` -environment variable to enable plugin functionality. (Any non-empty -value will work!) - -When you specify `KUBEBUILDER_ENABLE_PLUGINS=1`, a flag `--pattern` will become -available for resource generation. Specifying `--pattern=addon` will change -resource code generation to generate code that follows the addon pattern, as -being developed in the -[cluster-addons](https://github.com/kubernetes-sigs/cluster-addons) -subproject. - -The `pattern=addon` plugin is intended to serve both as an example of a plugin, -and as a real-world use case for driving development of the plugin system. We -don't intend for the plugin system to become an emacs competitor, but it must be -sufficiently flexible to support the various patterns of operators that -kubebuilder will generate. - -## Plugin model - -We intend for plugins to be packaged in a separate binary, which will be -executed by the `kubebuilder` main binary. Data will be piped to the binary via -stdin, and returned over stdout. The serialization format will likely either be -yaml or json (to be determined!). - -While we are developing this functionality though, we are developing it using an -in-process golang interface named `Plugin`, defined in -[pkg/plugin/scaffold/scaffold.go](../pkg/plugin/scaffold/scaffold.go). The interface is a -simple single-method interface that is intended to mirror the data-in / data-out -approach that will be used when executing a plugin in a separate binary. When -we have more stability of the plugin, we intend to replace the in-process -implementation with a implementation that `exec`s a plugin in a separate binary. - -The approach being prototyped is that we pass a model of the full state of the -generation world to the Plugin, which returns the full state of the generation -world after making appropriate changes. We are starting to define a `model` -package which includes a `Universe` comprising the various `File`s that are -being generated, along with the inputs like the `Boilerplate` and the `Resource` -we are currently generating. A plugin can change the `Contents` of `File`s, or -add/remove `File`s entirely. diff --git a/plugins/addon/helpers.go b/plugins/addon/helpers.go deleted file mode 100644 index 48f91e89d8d..00000000000 --- a/plugins/addon/helpers.go +++ /dev/null @@ -1,88 +0,0 @@ -package addon - -import ( - "bytes" - "fmt" - "strings" - "text/template" - - "github.com/gobuffalo/flect" - - "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -// This file gathers functions that are likely to be useful to other -// plugins. Once we have validated they are used in more than one -// place, we can promote them to a shared location. - -// PluginFunc executes a step of Plugin -type PluginFunc func(u *model.Universe) error - -// AddFile adds the specified file to the model. -// If the file exists the function returns false and does not modify the Universe -// If the file does not exist, the function returns true and adds the file to the Universe -// If there is a problem with the file the function returns an error -func AddFile(u *model.Universe, add *file.File) (bool, error) { - p := add.Path - if p == "" { - return false, fmt.Errorf("path must be set") - } - - if _, found := u.Files[p]; found { - return false, nil - } - - u.Files[p] = add - return true, nil -} - -// ReplaceFileIfExists replaces the specified file in the model by path -// Returns true if the file was replaced. -func ReplaceFileIfExists(u *model.Universe, add *file.File) bool { - p := add.Path - if p == "" { - panic("path must be set") - } - - if _, found := u.Files[p]; found { - u.Files[p] = add - return true - } - - return false -} - -// ReplaceFile replaces the specified file in the model by path -// If the file does not exist, it returns an error -func ReplaceFile(u *model.Universe, add *file.File) error { - found := ReplaceFileIfExists(u, add) - if !found { - return fmt.Errorf("file not found %q", add.Path) - } - return nil -} - -// DefaultTemplateFunctions returns a map of template helpers -func DefaultTemplateFunctions() template.FuncMap { - return template.FuncMap{ - "title": strings.Title, - "lower": strings.ToLower, - "plural": flect.Pluralize, - } -} - -// RunTemplate parses a template -func RunTemplate(templateName, templateValue string, data interface{}, funcMap template.FuncMap) (string, error) { - t, err := template.New(templateName).Funcs(funcMap).Parse(templateValue) - if err != nil { - return "", fmt.Errorf("error building template %s: %v", templateName, err) - } - - var b bytes.Buffer - if err := t.Execute(&b, data); err != nil { - return "", fmt.Errorf("error rending template %s: %v", templateName, err) - } - - return b.String(), nil -} diff --git a/plugins/addon/manifest.go b/plugins/addon/manifest.go deleted file mode 100644 index 227f1300ef1..00000000000 --- a/plugins/addon/manifest.go +++ /dev/null @@ -1,50 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package addon - -import ( - "path/filepath" - "strings" - - "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -const exampleManifestVersion = "0.0.1" - -const exampleManifestContents = `# Placeholder manifest - replace with the manifest for your addon -` - -// ExampleManifest adds a model file for the manifest placeholder -func ExampleManifest(u *model.Universe) error { - packageName := getPackageName(u) - - m := &file.File{ - Path: filepath.Join("channels", "packages", packageName, exampleManifestVersion, "manifest.yaml"), - Contents: exampleManifestContents, - IfExistsAction: file.Skip, - } - - _, err := AddFile(u, m) - - return err -} - -// getPackageName returns the (default) name of the declarative package -func getPackageName(u *model.Universe) string { - return strings.ToLower(u.Resource.Kind) -} diff --git a/plugins/addon/plugin.go b/plugins/addon/plugin.go deleted file mode 100644 index 3da0363b929..00000000000 --- a/plugins/addon/plugin.go +++ /dev/null @@ -1,28 +0,0 @@ -package addon - -import ( - "sigs.k8s.io/kubebuilder/v3/pkg/model" -) - -// Plugin implements model.Plugin -type Plugin struct { -} - -// Pipe implements model.Plugin -func (p *Plugin) Pipe(u *model.Universe) error { - functions := []PluginFunc{ - ExampleManifest, - ExampleChannel, - ReplaceController, - ReplaceTypes, - } - - for _, fn := range functions { - if err := fn(u); err != nil { - return err - } - - } - - return nil -} diff --git a/test/testdata/generate.sh b/test/testdata/generate.sh index 608bc757ce0..e9a15ae0217 100755 --- a/test/testdata/generate.sh +++ b/test/testdata/generate.sh @@ -96,13 +96,10 @@ function scaffold_test_project { $kb create webhook --version v1 --kind Lakers --defaulting --programmatic-validation fi elif [[ $project =~ addon ]]; then - header_text 'enabling --pattern flag ...' - export KUBEBUILDER_ENABLE_PLUGINS=1 header_text 'Creating APIs ...' - $kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --make=false --pattern=addon - $kb create api --group crew --version v1 --kind FirstMate --controller=true --resource=true --make=false --pattern=addon - $kb create api --group crew --version v1 --kind Admiral --controller=true --resource=true --namespaced=false --make=false --pattern=addon - unset KUBEBUILDER_ENABLE_PLUGINS + $kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --make=false + $kb create api --group crew --version v1 --kind FirstMate --controller=true --resource=true --make=false + $kb create api --group crew --version v1 --kind Admiral --controller=true --resource=true --namespaced=false --make=false fi make generate manifests @@ -116,9 +113,9 @@ build_kb # Project version 2 uses plugin go/v2 (default). scaffold_test_project project-v2 --project-version=2 scaffold_test_project project-v2-multigroup --project-version=2 -scaffold_test_project project-v2-addon --project-version=2 +scaffold_test_project project-v2-addon --project-version=3 --plugins="go/v2,declarative" # Project version 3 (default) uses plugin go/v3 (default). scaffold_test_project project-v3 scaffold_test_project project-v3-multigroup -scaffold_test_project project-v3-addon +scaffold_test_project project-v3-addon --plugins="go/v3,declarative" scaffold_test_project project-v3-config --component-config diff --git a/testdata/project-v2-addon/PROJECT b/testdata/project-v2-addon/PROJECT index 77f34e78733..b6ab134abfa 100644 --- a/testdata/project-v2-addon/PROJECT +++ b/testdata/project-v2-addon/PROJECT @@ -1,13 +1,32 @@ domain: testproject.org +layout: go.kubebuilder.io/v2,declarative.kubebuilder.io/v1 +projectName: project-v2-addon repo: sigs.k8s.io/kubebuilder/testdata/project-v2-addon resources: -- group: crew +- api: + crdVersion: v1beta1 + namespaced: true + controller: true + domain: testproject.org + group: crew kind: Captain + path: sigs.k8s.io/kubebuilder/testdata/project-v2-addon/api/v1 version: v1 -- group: crew +- api: + crdVersion: v1beta1 + namespaced: true + controller: true + domain: testproject.org + group: crew kind: FirstMate + path: sigs.k8s.io/kubebuilder/testdata/project-v2-addon/api/v1 version: v1 -- group: crew +- api: + crdVersion: v1beta1 + controller: true + domain: testproject.org + group: crew kind: Admiral + path: sigs.k8s.io/kubebuilder/testdata/project-v2-addon/api/v1 version: v1 -version: "2" +version: "3" diff --git a/testdata/project-v3-addon/PROJECT b/testdata/project-v3-addon/PROJECT index f5b6f774bc0..53ba275352b 100644 --- a/testdata/project-v3-addon/PROJECT +++ b/testdata/project-v3-addon/PROJECT @@ -1,5 +1,5 @@ domain: testproject.org -layout: go.kubebuilder.io/v3 +layout: go.kubebuilder.io/v3,declarative.kubebuilder.io/v1 projectName: project-v3-addon repo: sigs.k8s.io/kubebuilder/testdata/project-v3-addon resources: