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 8046f9e1d13..232adcf5a8d 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 57197a3710a..f0c10a7a992 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 c63d5b1ca8c..2fabef9bac3 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/api.go b/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/scaffolds/api.go index ab7956e2337..5b315585142 100644 --- a/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/scaffolds/api.go +++ b/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/scaffolds/api.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/pflag" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugin/external" + "sigs.k8s.io/yaml" ) var ApiFlags = []external.Flag{ @@ -57,6 +58,40 @@ func ApiCmd(pr *external.PluginRequest) external.PluginResponse { flags.Parse(pr.Args) number, _ := flags.GetInt("number") + // Update the project config with GVK + cfg := PluginConfig{} + err := yaml.Unmarshal([]byte(pr.Config), &cfg) + if err != nil { + return external.PluginResponse{ + Error: true, + ErrorMsgs: []string{ + err.Error(), + }, + } + } + + // Create and append the new config info + newResource := ResourceData{ + Group: "group", + Domain: "my.domain", + Version: "v1", + Kind: "Externalpluginsample", + } + cfg.Resources = append(cfg.Resources, newResource) + + updatedConfig, err := yaml.Marshal(cfg) + if err != nil { + return external.PluginResponse{ + Error: true, + ErrorMsgs: []string{ + err.Error(), + }, + } + } + + // Update the PluginResponse with the modified config string + pluginResponse.Config = string(updatedConfig) + apiFile := api.NewApiFile(api.WithNumber(number)) // Phase 2 Plugins uses the concept of a "universe" to represent the filesystem for a plugin. 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 d6a0f8b07c8..c715768c403 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,15 @@ limitations under the License. package scaffolds import ( - "v1/scaffolds/internal/templates" + "os" + "path/filepath" "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/yaml" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin/external" + "v1/scaffolds/internal/templates" ) var InitFlags = []external.Flag{ @@ -58,6 +61,46 @@ func InitCmd(pr *external.PluginRequest) external.PluginResponse { flags.Parse(pr.Args) domain, _ := flags.GetString("domain") + // Update the project config with ProjectName + cfg := PluginConfig{} + err := yaml.Unmarshal([]byte(pr.Config), &cfg) + if err != nil { + return external.PluginResponse{ + Error: true, + ErrorMsgs: []string{ + err.Error(), + }, + } + } + + // Get current directory as the project name + cwd, err := os.Getwd() + if err != nil { + return external.PluginResponse{ + Error: true, + ErrorMsgs: []string{ + err.Error(), + }, + } + } + + dirName := filepath.Base(cwd) + + cfg.ProjectName = dirName + + updatedConfig, err := yaml.Marshal(cfg) + if err != nil { + return external.PluginResponse{ + Error: true, + ErrorMsgs: []string{ + err.Error(), + }, + } + } + + // Update the PluginResponse with the modified config string + pluginResponse.Config = string(updatedConfig) + 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/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/scaffolds/interface.go b/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/scaffolds/interface.go new file mode 100644 index 00000000000..28f92a6cb39 --- /dev/null +++ b/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/scaffolds/interface.go @@ -0,0 +1,29 @@ +/* +Copyright 2023 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 scaffolds + +type PluginConfig struct { + ProjectName string `json:"projectname,omitempty"` + Resources []ResourceData `json:"resources,omitempty"` +} + +type ResourceData struct { + Group string `json:"group,omitempty"` + Domain string `json:"domain,omitempty"` + Version string `json:"version"` + Kind string `json:"kind"` +} diff --git a/pkg/plugin/external/types.go b/pkg/plugin/external/types.go index e9ac27d746c..6c506c35776 100644 --- a/pkg/plugin/external/types.go +++ b/pkg/plugin/external/types.go @@ -35,6 +35,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 string `json:"config"` } // PluginResponse is returned to kubebuilder by the plugin and contains all files @@ -63,6 +66,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 string `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 bff39f67411..002cab3b104 100644 --- a/pkg/plugins/external/api.go +++ b/pkg/plugins/external/api.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" @@ -32,8 +33,9 @@ const ( ) type createAPISubcommand struct { - Path string - Args []string + Path string + Args []string + config config.Config } func (p *createAPISubcommand) InjectResource(*resource.Resource) error { @@ -56,10 +58,19 @@ func (p *createAPISubcommand) 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 *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 b048bbd5504..c1c8bdde1a5 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 a1bc759317e..66f33380bc3 100644 --- a/pkg/plugins/external/helpers.go +++ b/pkg/plugins/external/helpers.go @@ -30,9 +30,11 @@ 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" + "sigs.k8s.io/yaml" ) var outputGetter ExecOutputGetter = &execOutputGetter{} @@ -51,6 +53,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 +157,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 +176,22 @@ func handlePluginResponse(fs machinery.Filesystem, req external.PluginRequest, p return fmt.Errorf("error getting current directory: %v", err) } + // TODO: for debug only, would delete it + fmt.Println("This is the received config from plugin response!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", res.Config) + + // update the config + if res.Config != "" { + updatedConfig := p.GetConfig() + + if err := yaml.Unmarshal([]byte(res.Config), updatedConfig); err != nil { + return fmt.Errorf("error unmarshalling the updated config from PluginResponse: %w", err) + } + + if err := p.InjectConfig(updatedConfig); 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 221eef95ea6..c47131308ee 100644 --- a/pkg/plugins/external/init.go +++ b/pkg/plugins/external/init.go @@ -17,18 +17,23 @@ limitations under the License. package external import ( + "fmt" + "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" + "sigs.k8s.io/yaml" ) 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 +45,33 @@ func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) { } func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error { + configBytes, err := yaml.Marshal(p.config) + if err != nil { + return err + } + + fmt.Println("Scaffolding with config:", string(configBytes)) + req := external.PluginRequest{ APIVersion: defaultAPIVersion, Command: "init", Args: p.Args, + Config: string(configBytes), } - 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 af49ee06649..f89d95fc382 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 cdd39333638..40e8b85df85 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",