diff --git a/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/cmd/cmd.go b/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/cmd/cmd.go index 8046f9e1d1..232adcf5a8 100644 --- a/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/cmd/cmd.go +++ b/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/cmd/cmd.go @@ -22,9 +22,8 @@ import ( "io" "os" - "v1/scaffolds" - "sigs.k8s.io/kubebuilder/v3/pkg/plugin/external" + "v1/scaffolds" ) // Run will run the actual steps of the plugin diff --git a/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/go.mod b/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/go.mod index 57197a3710..f0c10a7a99 100644 --- a/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/go.mod +++ b/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( github.com/spf13/pflag v1.0.5 sigs.k8s.io/kubebuilder/v3 v3.14.0 + sigs.k8s.io/yaml v1.4.0 ) require ( @@ -14,3 +15,5 @@ require ( golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.17.0 // indirect ) + +replace sigs.k8s.io/kubebuilder/v3 => ../../../../../../../ diff --git a/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/go.sum b/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/go.sum index c63d5b1ca8..2fabef9bac 100644 --- a/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/go.sum +++ b/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/go.sum @@ -7,6 +7,7 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20230907193218-d3ddc7976beb h1:LCMfzVg3sflxTs4UvuP4D8CkoZnfHLe2qzqgDn/4OHs= @@ -38,11 +39,10 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -sigs.k8s.io/kubebuilder/v3 v3.14.0 h1:DVrHb6ADfGQKk/4NiMFOO7XIJK58maXhYIwlHSFy84I= -sigs.k8s.io/kubebuilder/v3 v3.14.0/go.mod h1:vh/9c7elEE2h0wB+Gxy1t63f8WuPrLO1ExoQR6ms8L0= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/scaffolds/init.go b/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/scaffolds/init.go index d6a0f8b07c..50bc17146a 100644 --- a/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/scaffolds/init.go +++ b/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/scaffolds/init.go @@ -16,12 +16,11 @@ limitations under the License. package scaffolds import ( - "v1/scaffolds/internal/templates" - "github.com/spf13/pflag" - + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugin/external" + "v1/scaffolds/internal/templates" ) var InitFlags = []external.Flag{ @@ -58,6 +57,31 @@ func InitCmd(pr *external.PluginRequest) external.PluginResponse { flags.Parse(pr.Args) domain, _ := flags.GetString("domain") + // Update the project config + cfg := pr.Config + + pluginChain := cfg.GetPluginChain() + pluginChain = append(pluginChain, pflag.Arg(0)) + cfg.SetPluginChain(pluginChain) + + if cfg.GetProjectName() == "" { + cfg.SetProjectName("externalplugin") + } + + rsc := resource.Resource{ + GVK: resource.GVK{ + Group: "group", + Version: "v1", + Kind: "Externalpluginsample", + Domain: "my.domain", + }, + } + + cfg.UpdateResource(rsc) + + // Update the PluginResponse with the modified updated config + pluginResponse.Config = cfg + initFile := templates.NewInitFile(templates.WithDomain(domain)) // Phase 2 Plugins uses the concept of a "universe" to represent the filesystem for a plugin. diff --git a/pkg/plugin/external/types.go b/pkg/plugin/external/types.go index e9ac27d746..0e708aec0f 100644 --- a/pkg/plugin/external/types.go +++ b/pkg/plugin/external/types.go @@ -16,7 +16,10 @@ limitations under the License. package external -import "sigs.k8s.io/kubebuilder/v3/pkg/plugin" +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" +) // PluginRequest contains all information kubebuilder received from the CLI // and plugins executed before it. @@ -35,6 +38,9 @@ type PluginRequest struct { // Universe represents the modified file contents that gets updated over a series of plugin runs // across the plugin chain. Initially, it starts out as empty. Universe map[string]string `json:"universe"` + + // Config stores the project configuration file. + Config config.Config `json:"config"` } // PluginResponse is returned to kubebuilder by the plugin and contains all files @@ -63,6 +69,9 @@ type PluginResponse struct { // Flags contains the plugin specific flags that the plugin returns to Kubebuilder when it receives // a request for a list of supported flags from Kubebuilder Flags []Flag `json:"flags,omitempty"` + + // Config stores the project configuration file. + Config config.Config `json:"config"` } // Flag is meant to represent a CLI flag that is used by Kubebuilder to define flags that are parsed diff --git a/pkg/plugins/external/api.go b/pkg/plugins/external/api.go index bff39f6741..dac2de1791 100644 --- a/pkg/plugins/external/api.go +++ b/pkg/plugins/external/api.go @@ -17,8 +17,11 @@ limitations under the License. package external import ( + "github.com/spf13/afero" "github.com/spf13/pflag" + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config/store/yaml" "sigs.k8s.io/kubebuilder/v3/pkg/machinery" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" @@ -32,8 +35,9 @@ const ( ) type createAPISubcommand struct { - Path string - Args []string + Path string + Args []string + config config.Config } func (p *createAPISubcommand) InjectResource(*resource.Resource) error { @@ -50,16 +54,31 @@ func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { } func (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error { + cfg := yaml.New(machinery.Filesystem{FS: afero.NewOsFs()}) + if err := cfg.Load(); err != nil { + return err + } + req := external.PluginRequest{ APIVersion: defaultAPIVersion, Command: "create api", Args: p.Args, + Config: cfg.Config(), } - err := handlePluginResponse(fs, req, p.Path) + err := handlePluginResponse(fs, req, p.Path, p) if err != nil { return err } return nil } + +func (p *createAPISubcommand) InjectConfig(c config.Config) error { + p.config = c + return nil +} + +func (p *createAPISubcommand) GetConfig() config.Config { + return p.config +} diff --git a/pkg/plugins/external/edit.go b/pkg/plugins/external/edit.go index b048bbd550..c1c8bdde1a 100644 --- a/pkg/plugins/external/edit.go +++ b/pkg/plugins/external/edit.go @@ -19,6 +19,7 @@ package external import ( "github.com/spf13/pflag" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/machinery" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugin/external" @@ -27,8 +28,9 @@ import ( var _ plugin.EditSubcommand = &editSubcommand{} type editSubcommand struct { - Path string - Args []string + Path string + Args []string + config config.Config } func (p *editSubcommand) UpdateMetadata(_ plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { @@ -46,10 +48,19 @@ func (p *editSubcommand) Scaffold(fs machinery.Filesystem) error { Args: p.Args, } - err := handlePluginResponse(fs, req, p.Path) + err := handlePluginResponse(fs, req, p.Path, p) if err != nil { return err } return nil } + +func (p *editSubcommand) InjectConfig(c config.Config) error { + p.config = c + return nil +} + +func (p *editSubcommand) GetConfig() config.Config { + return p.config +} diff --git a/pkg/plugins/external/helpers.go b/pkg/plugins/external/helpers.go index a1bc759317..b6acfae153 100644 --- a/pkg/plugins/external/helpers.go +++ b/pkg/plugins/external/helpers.go @@ -30,6 +30,7 @@ import ( "github.com/spf13/afero" "github.com/spf13/pflag" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/machinery" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugin/external" @@ -51,6 +52,12 @@ type ExecOutputGetter interface { type execOutputGetter struct{} +// PluginConfigHandler is an interface to update the config modified by external plugin. +type PluginConfigHandler interface { + GetConfig() config.Config + InjectConfig(config.Config) error +} + func (e *execOutputGetter) GetExecOutput(request []byte, path string) ([]byte, error) { cmd := exec.Command(path) //nolint:gosec cmd.Stdin = bytes.NewBuffer(request) @@ -149,7 +156,8 @@ func getUniverseMap(fs machinery.Filesystem) (map[string]string, error) { return universe, nil } -func handlePluginResponse(fs machinery.Filesystem, req external.PluginRequest, path string) error { +// nolint:lll +func handlePluginResponse(fs machinery.Filesystem, req external.PluginRequest, path string, p PluginConfigHandler) error { var err error req.Universe, err = getUniverseMap(fs) @@ -167,6 +175,11 @@ func handlePluginResponse(fs machinery.Filesystem, req external.PluginRequest, p return fmt.Errorf("error getting current directory: %v", err) } + // update the config + if err := p.InjectConfig(res.Config); err != nil { + return fmt.Errorf("error injecting the updated config from PluginResponse: %w", err) + } + for filename, data := range res.Universe { path := filepath.Join(currentDir, filename) dir := filepath.Dir(path) diff --git a/pkg/plugins/external/init.go b/pkg/plugins/external/init.go index 221eef95ea..b587b07902 100644 --- a/pkg/plugins/external/init.go +++ b/pkg/plugins/external/init.go @@ -17,8 +17,13 @@ limitations under the License. package external import ( + "os" + + "github.com/spf13/afero" "github.com/spf13/pflag" + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config/store/yaml" "sigs.k8s.io/kubebuilder/v3/pkg/machinery" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugin/external" @@ -27,8 +32,9 @@ import ( var _ plugin.InitSubcommand = &initSubcommand{} type initSubcommand struct { - Path string - Args []string + Path string + Args []string + config config.Config } func (p *initSubcommand) UpdateMetadata(_ plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { @@ -40,16 +46,51 @@ func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) { } func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error { + fileName := "PROJECT" + + if _, err := os.Stat(fileName); os.IsNotExist(err) { + file, err := os.Create(fileName) + if err != nil { + return err + } + defer func() { + if cerr := file.Close(); cerr != nil { + if err == nil { + err = cerr + } + } + }() + } + + cfg := yaml.New(machinery.Filesystem{FS: afero.NewOsFs()}) + if err := cfg.New(config.Version{Number: 3}); err != nil { + return err + } + + if err := cfg.Load(); err != nil { + return err + } + req := external.PluginRequest{ APIVersion: defaultAPIVersion, Command: "init", Args: p.Args, + Config: cfg.Config(), } - err := handlePluginResponse(fs, req, p.Path) + err := handlePluginResponse(fs, req, p.Path, p) if err != nil { return err } return nil } + +func (p *initSubcommand) InjectConfig(c config.Config) error { + p.config = c + return nil +} + +func (p *initSubcommand) GetConfig() config.Config { + return p.config +} diff --git a/pkg/plugins/external/webhook.go b/pkg/plugins/external/webhook.go index af49ee0664..f89d95fc38 100644 --- a/pkg/plugins/external/webhook.go +++ b/pkg/plugins/external/webhook.go @@ -19,6 +19,7 @@ package external import ( "github.com/spf13/pflag" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/machinery" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" @@ -28,8 +29,9 @@ import ( var _ plugin.CreateWebhookSubcommand = &createWebhookSubcommand{} type createWebhookSubcommand struct { - Path string - Args []string + Path string + Args []string + config config.Config } func (p *createWebhookSubcommand) InjectResource(*resource.Resource) error { @@ -52,10 +54,19 @@ func (p *createWebhookSubcommand) Scaffold(fs machinery.Filesystem) error { Args: p.Args, } - err := handlePluginResponse(fs, req, p.Path) + err := handlePluginResponse(fs, req, p.Path, p) if err != nil { return err } return nil } + +func (p *createWebhookSubcommand) InjectConfig(c config.Config) error { + p.config = c + return nil +} + +func (p *createWebhookSubcommand) GetConfig() config.Config { + return p.config +} diff --git a/test/e2e/externalplugin/generate_test.go b/test/e2e/externalplugin/generate_test.go index cdd3933363..40e8b85df8 100644 --- a/test/e2e/externalplugin/generate_test.go +++ b/test/e2e/externalplugin/generate_test.go @@ -74,6 +74,14 @@ func GenerateProject(kbc *utils.TestContext) { ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Check initFile.txt should return no error.") ExpectWithOffset(1, initFileContainsExpr).To(BeTrue(), "The init file does not contain the expected expression.") + var initSubcommandConfigTmpl = "projectName" + + initSubcommandConfigContainsExpr, err := pluginutil.HasFileContentWith( + filepath.Join(kbc.Dir, "PROJECT"), initSubcommandConfigTmpl) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Check PROJECT should return no error.") + //nolint:lll + ExpectWithOffset(1, initSubcommandConfigContainsExpr).To(BeTrue(), "The PROJECT file does not contain the expected config with the init subcommand.") + By("creating API definition") err = kbc.CreateAPI( "--plugins", "sampleexternalplugin/v1", @@ -90,6 +98,19 @@ func GenerateProject(kbc *utils.TestContext) { ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Check apiFile.txt should return no error.") ExpectWithOffset(1, apiFileContainsExpr).To(BeTrue(), "The api file does not contain the expected expression.") + var apiSubcommandConfigTmpl = ` +resources: +- domain: my.domain + group: group + kind: Externalpluginsample + version: v1` + + apiSubcommandConfigContainsExpr, err := pluginutil.HasFileContentWith( + filepath.Join(kbc.Dir, "PROJECT"), apiSubcommandConfigTmpl) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Check PROJECT should return no error.") + //nolint:lll + ExpectWithOffset(1, apiSubcommandConfigContainsExpr).To(BeTrue(), "The PROJECT file does not contain the expected config with the create api subcommand.") + By("scaffolding webhook") err = kbc.CreateWebhook( "--plugins", "sampleexternalplugin/v1",