diff --git a/docs/book/src/plugins/extending/external-plugins.md b/docs/book/src/plugins/extending/external-plugins.md index 5010928f5b9..bfbdfc74f76 100644 --- a/docs/book/src/plugins/extending/external-plugins.md +++ b/docs/book/src/plugins/extending/external-plugins.md @@ -26,10 +26,19 @@ standard I/O. Any language can be used to create the plugin, as long as it follows the [PluginRequest][code-plugin-external] and [PluginResponse][code-plugin-external] structures. -### PluginRequest - `PluginRequest` contains the data collected from the CLI and any previously executed plugins. Kubebuilder sends this data as a JSON object to the external plugin via `stdin`. +**Fields:** +- `apiVersion`: Version of the PluginRequest schema. +- `args`: Command-line arguments passed to the plugin. +- `command`: The subcommand being executed (e.g., `init`, `create api`, `create webhook`, `edit`). +- `universe`: Map of file paths to contents, updated across the plugin chain. +- `pluginChain` (optional): Array of plugin keys in the order they were executed. External plugins can inspect this to tailor behavior based on other plugins that ran (for example, `go.kubebuilder.io/v4` or `kustomize.common.kubebuilder.io/v2`). +- `config` (optional): Serialized PROJECT file configuration for the current project. Use it to inspect metadata, existing resources, or plugin-specific settings. Kubebuilder omits this field before the PROJECT file exists—typically during the first `init`—so plugins should check for its presence. + + +**Note:** Whenever Kubebuilder has a PROJECT file available (for example during `create api`, `create webhook`, `edit`, or a subsequent `init` run), `PluginRequest` includes the `config` field. During the very first `init` run the field is omitted because the PROJECT file does not exist yet. + **Example `PluginRequest` (triggered by `kubebuilder init --plugins go/v4,sampleexternalplugin/v1 --domain my.domain`):** ```json @@ -42,12 +51,25 @@ structures. } ``` -**Fields:** -- `apiVersion`: Version of the PluginRequest schema. -- `args`: Command-line arguments passed to the plugin. -- `command`: The subcommand being executed (e.g., `init`, `create api`, `create webhook`, `edit`). -- `universe`: Map of file paths to contents, updated across the plugin chain. -- `pluginChain` (optional): Array of plugin keys in the chain. This allows external plugins to determine which other plugins are being used. For example, a plugin can check if `go.kubebuilder.io/v4` or `go.kubebuilder.io/v3` is in the chain to adjust its scaffolding accordingly. +**Example `PluginRequest` for `create api` (includes `config`):** +```json +{ + "apiVersion": "v1alpha1", + "args": ["--group", "crew", "--version", "v1", "--kind", "Captain"], + "command": "create api", + "universe": {}, + "pluginChain": ["go.kubebuilder.io/v4", "kustomize.common.kubebuilder.io/v2", "sampleexternalplugin/v1"], + "config": { + "domain": "my.domain", + "repo": "github.com/example/my-project", + "projectName": "my-project", + "version": "3", + "layout": ["go.kubebuilder.io/v4"], + "multigroup": false, + "resources": [] + } +} +``` ### PluginResponse diff --git a/pkg/cli/cli_test.go b/pkg/cli/cli_test.go index 451aaab635e..8027620e1ae 100644 --- a/pkg/cli/cli_test.go +++ b/pkg/cli/cli_test.go @@ -77,6 +77,18 @@ func hasSubCommand(cmd *cobra.Command, name string) bool { return false } +type pluginChainCapturingSubcommand struct { + pluginChain []string +} + +func (s *pluginChainCapturingSubcommand) Scaffold(machinery.Filesystem) error { + return nil +} + +func (s *pluginChainCapturingSubcommand) SetPluginChain(chain []string) { + s.pluginChain = append([]string(nil), chain...) +} + var _ = Describe("CLI", func() { var ( c *CLI @@ -431,6 +443,42 @@ plugins: }) }) + Context("applySubcommandHooks", func() { + var ( + cmd *cobra.Command + sub1, sub2 *pluginChainCapturingSubcommand + tuples []keySubcommandTuple + chainKeys []string + ) + + BeforeEach(func() { + cmd = &cobra.Command{} + sub1 = &pluginChainCapturingSubcommand{} + sub2 = &pluginChainCapturingSubcommand{} + tuples = []keySubcommandTuple{ + {key: "alpha.kubebuilder.io/v1", subcommand: sub1}, + {key: "beta.kubebuilder.io/v1", subcommand: sub2}, + } + chainKeys = []string{"alpha.kubebuilder.io/v1", "beta.kubebuilder.io/v1"} + }) + + It("sets the plugin chain on subcommands", func() { + c.applySubcommandHooks(cmd, tuples, "test", false) + + Expect(sub1.pluginChain).To(Equal(chainKeys)) + Expect(sub2.pluginChain).To(Equal(chainKeys)) + }) + + It("sets the plugin chain when creating a new configuration", func() { + c.resolvedPlugins = makeMockPluginsFor(projectVersion, chainKeys...) + + c.applySubcommandHooks(cmd, tuples, "test", true) + + Expect(sub1.pluginChain).To(Equal(chainKeys)) + Expect(sub2.pluginChain).To(Equal(chainKeys)) + }) + }) + Context("New", func() { var c *CLI var err error diff --git a/pkg/cli/cmd_helpers.go b/pkg/cli/cmd_helpers.go index acd790e3a84..c7969438e01 100644 --- a/pkg/cli/cmd_helpers.go +++ b/pkg/cli/cmd_helpers.go @@ -72,6 +72,10 @@ type keySubcommandTuple struct { skip bool } +type pluginChainSetter interface { + SetPluginChain([]string) +} + // 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, @@ -107,6 +111,16 @@ func (c *CLI) applySubcommandHooks( errorMessage string, createConfig bool, ) { + commandPluginChain := make([]string, len(subcommands)) + for i, tuple := range subcommands { + commandPluginChain[i] = tuple.key + } + for _, tuple := range subcommands { + if setter, ok := tuple.subcommand.(pluginChainSetter); ok { + setter.SetPluginChain(commandPluginChain) + } + } + // In case we create a new project configuration we need to compute the plugin chain. pluginChain := make([]string, 0, len(c.resolvedPlugins)) if createConfig { diff --git a/pkg/plugin/external/types.go b/pkg/plugin/external/types.go index 6aa9757cc6c..974cf44252b 100644 --- a/pkg/plugin/external/types.go +++ b/pkg/plugin/external/types.go @@ -40,6 +40,10 @@ type PluginRequest struct { // This allows external plugins to know which other plugins are in use. // Format: ["go.kubebuilder.io/v4", "kustomize.common.kubebuilder.io/v2"] PluginChain []string `json:"pluginChain,omitempty"` + + // Config contains the PROJECT file config. This field may be empty if the + // project is being initialized and the PROJECT file has not been created yet. + Config map[string]interface{} `json:"config,omitempty"` } // PluginResponse is returned to kubebuilder by the plugin and contains all files diff --git a/pkg/plugins/external/api.go b/pkg/plugins/external/api.go index c4b89ddbbad..dc34a8d7c03 100644 --- a/pkg/plugins/external/api.go +++ b/pkg/plugins/external/api.go @@ -36,14 +36,33 @@ type createAPISubcommand struct { Path string Args []string pluginChain []string + config config.Config } -// InjectConfig injects the project configuration to access plugin chain information +// InjectConfig injects the project configuration so external plugins can read the PROJECT file. func (p *createAPISubcommand) InjectConfig(c config.Config) error { - p.pluginChain = c.GetPluginChain() + p.config = c + + if c == nil { + return nil + } + + if chain := c.GetPluginChain(); len(chain) > 0 { + p.pluginChain = append([]string(nil), chain...) + } + return nil } +func (p *createAPISubcommand) SetPluginChain(chain []string) { + if len(chain) == 0 { + p.pluginChain = nil + return + } + + p.pluginChain = append([]string(nil), chain...) +} + func (p *createAPISubcommand) InjectResource(*resource.Resource) error { // Do nothing since resource flags are passed to the external plugin directly. return nil @@ -65,7 +84,7 @@ func (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error { PluginChain: p.pluginChain, } - err := handlePluginResponse(fs, req, p.Path) + err := handlePluginResponse(fs, req, p.Path, p.config) if err != nil { return err } diff --git a/pkg/plugins/external/edit.go b/pkg/plugins/external/edit.go index c213bfec9b0..6de0dc8158a 100644 --- a/pkg/plugins/external/edit.go +++ b/pkg/plugins/external/edit.go @@ -32,14 +32,33 @@ type editSubcommand struct { Path string Args []string pluginChain []string + config config.Config } // InjectConfig injects the project configuration to access plugin chain information func (p *editSubcommand) InjectConfig(c config.Config) error { - p.pluginChain = c.GetPluginChain() + p.config = c + + if c == nil { + return nil + } + + if chain := c.GetPluginChain(); len(chain) > 0 { + p.pluginChain = append([]string(nil), chain...) + } + return nil } +func (p *editSubcommand) SetPluginChain(chain []string) { + if len(chain) == 0 { + p.pluginChain = nil + return + } + + p.pluginChain = append([]string(nil), chain...) +} + func (p *editSubcommand) UpdateMetadata(_ plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { setExternalPluginMetadata("edit", p.Path, subcmdMeta) } @@ -56,7 +75,7 @@ func (p *editSubcommand) Scaffold(fs machinery.Filesystem) error { PluginChain: p.pluginChain, } - err := handlePluginResponse(fs, req, p.Path) + err := handlePluginResponse(fs, req, p.Path, p.config) if err != nil { return err } diff --git a/pkg/plugins/external/external_test.go b/pkg/plugins/external/external_test.go index 60387b7f728..12423bb259c 100644 --- a/pkg/plugins/external/external_test.go +++ b/pkg/plugins/external/external_test.go @@ -28,11 +28,45 @@ import ( "github.com/spf13/afero" "github.com/spf13/pflag" + "sigs.k8s.io/kubebuilder/v4/pkg/config" + v3 "sigs.k8s.io/kubebuilder/v4/pkg/config/v3" "sigs.k8s.io/kubebuilder/v4/pkg/machinery" "sigs.k8s.io/kubebuilder/v4/pkg/plugin" "sigs.k8s.io/kubebuilder/v4/pkg/plugin/external" ) +type chainAwareSubcommand interface { + SetPluginChain([]string) + InjectConfig(config.Config) error +} + +var pluginChainTestCases = []struct { + name string + new func() chainAwareSubcommand + get func(chainAwareSubcommand) []string +}{ + { + name: "init", + new: func() chainAwareSubcommand { return &initSubcommand{} }, + get: func(sub chainAwareSubcommand) []string { return sub.(*initSubcommand).pluginChain }, + }, + { + name: "edit", + new: func() chainAwareSubcommand { return &editSubcommand{} }, + get: func(sub chainAwareSubcommand) []string { return sub.(*editSubcommand).pluginChain }, + }, + { + name: "create api", + new: func() chainAwareSubcommand { return &createAPISubcommand{} }, + get: func(sub chainAwareSubcommand) []string { return sub.(*createAPISubcommand).pluginChain }, + }, + { + name: "create webhook", + new: func() chainAwareSubcommand { return &createWebhookSubcommand{} }, + get: func(sub chainAwareSubcommand) []string { return sub.(*createWebhookSubcommand).pluginChain }, + }, +} + func TestExternalPlugin(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Scaffold") @@ -100,6 +134,26 @@ func (m *mockInValidOsWdGetter) GetCurrentDir() (string, error) { return "", fmt.Errorf("error getting current directory") } +type mockConfigOutputGetter struct { + capturedRequest *external.PluginRequest +} + +var _ ExecOutputGetter = &mockConfigOutputGetter{} + +func (m *mockConfigOutputGetter) GetExecOutput(reqBytes []byte, _ string) ([]byte, error) { + m.capturedRequest = &external.PluginRequest{} + if err := json.Unmarshal(reqBytes, m.capturedRequest); err != nil { + return nil, fmt.Errorf("error unmarshalling request: %w", err) + } + + return []byte(`{ + "command": "init", + "error": false, + "error_msg": "none", + "universe": {"LICENSE": "Apache 2.0 License\n"} + }`), nil +} + type mockValidFlagOutputGetter struct{} func (m *mockValidFlagOutputGetter) GetExecOutput(_ []byte, _ string) ([]byte, error) { @@ -780,6 +834,42 @@ var _ = Describe("Run external plugin using Scaffold", func() { }) }) + Context("plugin chain propagation", func() { + for _, tc := range pluginChainTestCases { + caseData := tc + Context(caseData.name, func() { + It("keeps the CLI-provided chain when config omits pluginChain", func() { + sub := caseData.new() + + cliChain := []string{"cli.plugin/v1"} + sub.SetPluginChain(cliChain) + + cliChain[0] = "mutated" + Expect(caseData.get(sub)).To(Equal([]string{"cli.plugin/v1"})) + + cfg := v3.New() + Expect(sub.InjectConfig(cfg)).To(Succeed()) + Expect(caseData.get(sub)).To(Equal([]string{"cli.plugin/v1"})) + + sub.SetPluginChain(nil) + Expect(caseData.get(sub)).To(BeNil()) + }) + + It("prefers the config plugin chain when present", func() { + sub := caseData.new() + sub.SetPluginChain([]string{"cli.plugin/v1"}) + + cfg := v3.New() + expected := []string{"config.plugin/v2"} + Expect(cfg.SetPluginChain(expected)).To(Succeed()) + + Expect(sub.InjectConfig(cfg)).To(Succeed()) + Expect(caseData.get(sub)).To(Equal(expected)) + }) + }) + } + }) + Context("PluginChain is passed to external plugin", func() { var ( pluginChainCaptured []string @@ -858,6 +948,237 @@ var _ = Describe("Run external plugin using Scaffold", func() { Expect(pluginChainCaptured).To(Equal([]string{"go.kubebuilder.io/v4", "declarative.go.kubebuilder.io/v1"})) }) }) + + Context("with config injection", func() { + const filePerm os.FileMode = 755 + var ( + pluginFileName string + args []string + f afero.File + fs machinery.Filesystem + mockGetter *mockConfigOutputGetter + cfg *v3.Cfg + expectedChain []string + + err error + ) + + BeforeEach(func() { + mockGetter = &mockConfigOutputGetter{} + outputGetter = mockGetter + currentDirGetter = &mockValidOsWdGetter{} + fs = machinery.Filesystem{ + FS: afero.NewMemMapFs(), + } + + pluginFileName = "externalPlugin.sh" + pluginFilePath := filepath.Join("tmp", "externalPlugin", pluginFileName) + + err = fs.FS.MkdirAll(filepath.Dir(pluginFilePath), filePerm) + Expect(err).ToNot(HaveOccurred()) + + f, err = fs.FS.Create(pluginFilePath) + Expect(err).ToNot(HaveOccurred()) + Expect(f).ToNot(BeNil()) + + _, err = fs.FS.Stat(pluginFilePath) + Expect(err).ToNot(HaveOccurred()) + + args = []string{"--domain", "example.com"} + + cfg = &v3.Cfg{ + Version: v3.Version, + Domain: "test.domain", + Repository: "github.com/test/repo", + Name: "test-project", + } + + expectedChain = []string{"go.kubebuilder.io/v4", "kustomize.common.kubebuilder.io/v2"} + Expect(cfg.SetPluginChain(expectedChain)).To(Succeed()) + }) + + It("should pass config to external plugin on init subcommand", func() { + i := initSubcommand{ + Path: pluginFileName, + Args: args, + } + + Expect(i.InjectConfig(cfg)).To(Succeed()) + + err = i.Scaffold(fs) + Expect(err).ToNot(HaveOccurred()) + + Expect(mockGetter.capturedRequest).ToNot(BeNil()) + Expect(mockGetter.capturedRequest.Config).ToNot(BeNil()) + Expect(mockGetter.capturedRequest.Config["domain"]).To(Equal("test.domain")) + Expect(mockGetter.capturedRequest.Config["repo"]).To(Equal("github.com/test/repo")) + Expect(mockGetter.capturedRequest.Config["projectName"]).To(Equal("test-project")) + Expect(mockGetter.capturedRequest.PluginChain).To(Equal(expectedChain)) + }) + + It("should pass config to external plugin on create api subcommand", func() { + c := createAPISubcommand{ + Path: pluginFileName, + Args: args, + } + + Expect(c.InjectConfig(cfg)).To(Succeed()) + + err = c.Scaffold(fs) + Expect(err).ToNot(HaveOccurred()) + + Expect(mockGetter.capturedRequest).ToNot(BeNil()) + Expect(mockGetter.capturedRequest.Config).ToNot(BeNil()) + Expect(mockGetter.capturedRequest.Config["domain"]).To(Equal("test.domain")) + Expect(mockGetter.capturedRequest.PluginChain).To(Equal(expectedChain)) + }) + + It("should pass config to external plugin on create webhook subcommand", func() { + c := createWebhookSubcommand{ + Path: pluginFileName, + Args: args, + } + + Expect(c.InjectConfig(cfg)).To(Succeed()) + + err = c.Scaffold(fs) + Expect(err).ToNot(HaveOccurred()) + + Expect(mockGetter.capturedRequest).ToNot(BeNil()) + Expect(mockGetter.capturedRequest.Config).ToNot(BeNil()) + Expect(mockGetter.capturedRequest.Config["domain"]).To(Equal("test.domain")) + Expect(mockGetter.capturedRequest.PluginChain).To(Equal(expectedChain)) + }) + + It("should pass config to external plugin on edit subcommand", func() { + e := editSubcommand{ + Path: pluginFileName, + Args: args, + } + + Expect(e.InjectConfig(cfg)).To(Succeed()) + + err = e.Scaffold(fs) + Expect(err).ToNot(HaveOccurred()) + + Expect(mockGetter.capturedRequest).ToNot(BeNil()) + Expect(mockGetter.capturedRequest.Config).ToNot(BeNil()) + Expect(mockGetter.capturedRequest.Config["domain"]).To(Equal("test.domain")) + Expect(mockGetter.capturedRequest.PluginChain).To(Equal(expectedChain)) + }) + + It("should handle nil config gracefully", func() { + i := initSubcommand{ + Path: pluginFileName, + Args: args, + } + + Expect(i.InjectConfig(nil)).To(Succeed()) + + err = i.Scaffold(fs) + Expect(err).ToNot(HaveOccurred()) + + Expect(mockGetter.capturedRequest).ToNot(BeNil()) + Expect(mockGetter.capturedRequest.Config).To(BeNil()) + Expect(mockGetter.capturedRequest.PluginChain).To(BeNil()) + }) + }) + + Context("PluginChain is passed to external plugin", func() { + var ( + pluginChainCaptured []string + mockOutputGetter *mockPluginChainCaptureGetter + ) + + BeforeEach(func() { + pluginChainCaptured = nil + mockOutputGetter = &mockPluginChainCaptureGetter{ + capturedChain: &pluginChainCaptured, + } + outputGetter = mockOutputGetter + currentDirGetter = &mockValidOsWdGetter{} + }) + + It("should pass plugin chain to init subcommand", func() { + fs := machinery.Filesystem{ + FS: afero.NewMemMapFs(), + } + + cfg := &v3.Cfg{Version: v3.Version} + Expect(cfg.SetPluginChain([]string{"go.kubebuilder.io/v4", "kustomize.common.kubebuilder.io/v2"})).To(Succeed()) + + i := initSubcommand{ + Path: "test.sh", + Args: []string{"--domain", "example.com"}, + } + + Expect(i.InjectConfig(cfg)).To(Succeed()) + + err := i.Scaffold(fs) + Expect(err).ToNot(HaveOccurred()) + Expect(pluginChainCaptured).To(Equal([]string{"go.kubebuilder.io/v4", "kustomize.common.kubebuilder.io/v2"})) + }) + + It("should pass plugin chain to create api subcommand", func() { + fs := machinery.Filesystem{ + FS: afero.NewMemMapFs(), + } + + cfg := &v3.Cfg{Version: v3.Version} + Expect(cfg.SetPluginChain([]string{"go.kubebuilder.io/v4"})).To(Succeed()) + + c := createAPISubcommand{ + Path: "test.sh", + Args: []string{"--group", "apps", "--version", "v1", "--kind", "MyKind"}, + } + + Expect(c.InjectConfig(cfg)).To(Succeed()) + + err := c.Scaffold(fs) + Expect(err).ToNot(HaveOccurred()) + Expect(pluginChainCaptured).To(Equal([]string{"go.kubebuilder.io/v4"})) + }) + + It("should pass plugin chain to create webhook subcommand", func() { + fs := machinery.Filesystem{ + FS: afero.NewMemMapFs(), + } + + cfg := &v3.Cfg{Version: v3.Version} + Expect(cfg.SetPluginChain([]string{"go.kubebuilder.io/v3"})).To(Succeed()) + + w := createWebhookSubcommand{ + Path: "test.sh", + Args: []string{"--group", "apps", "--version", "v1", "--kind", "MyKind"}, + } + + Expect(w.InjectConfig(cfg)).To(Succeed()) + + err := w.Scaffold(fs) + Expect(err).ToNot(HaveOccurred()) + Expect(pluginChainCaptured).To(Equal([]string{"go.kubebuilder.io/v3"})) + }) + + It("should pass plugin chain to edit subcommand", func() { + fs := machinery.Filesystem{ + FS: afero.NewMemMapFs(), + } + + cfg := &v3.Cfg{Version: v3.Version} + Expect(cfg.SetPluginChain([]string{"go.kubebuilder.io/v4", "declarative.go.kubebuilder.io/v1"})).To(Succeed()) + + e := editSubcommand{ + Path: "test.sh", + Args: []string{"--multigroup"}, + } + + Expect(e.InjectConfig(cfg)).To(Succeed()) + + err := e.Scaffold(fs) + Expect(err).ToNot(HaveOccurred()) + Expect(pluginChainCaptured).To(Equal([]string{"go.kubebuilder.io/v4", "declarative.go.kubebuilder.io/v1"})) + }) + }) }) func getFlags() []external.Flag { diff --git a/pkg/plugins/external/helpers.go b/pkg/plugins/external/helpers.go index 7f59541a75a..d6fad7c3792 100644 --- a/pkg/plugins/external/helpers.go +++ b/pkg/plugins/external/helpers.go @@ -30,7 +30,9 @@ import ( "github.com/spf13/afero" "github.com/spf13/pflag" + "sigs.k8s.io/yaml" + "sigs.k8s.io/kubebuilder/v4/pkg/config" "sigs.k8s.io/kubebuilder/v4/pkg/machinery" "sigs.k8s.io/kubebuilder/v4/pkg/plugin" "sigs.k8s.io/kubebuilder/v4/pkg/plugin/external" @@ -149,7 +151,7 @@ func getUniverseMap(fs machinery.Filesystem) (map[string]string, error) { return universe, nil } -func handlePluginResponse(fs machinery.Filesystem, req external.PluginRequest, path string) error { +func handlePluginResponse(fs machinery.Filesystem, req external.PluginRequest, path string, cfg config.Config) error { var err error req.Universe, err = getUniverseMap(fs) @@ -157,6 +159,22 @@ func handlePluginResponse(fs machinery.Filesystem, req external.PluginRequest, p return fmt.Errorf("error getting universe map: %w", err) } + // Marshal config to include in the request if config is provided + if cfg != nil { + var configData []byte + configData, err = cfg.MarshalYAML() + if err != nil { + return fmt.Errorf("error marshaling config: %w", err) + } + + var configMap map[string]interface{} + if err = yaml.Unmarshal(configData, &configMap); err != nil { + return fmt.Errorf("error unmarshaling config to map: %w", err) + } + + req.Config = configMap + } + res, err := makePluginRequest(req, path) if err != nil { return fmt.Errorf("error making request to external plugin: %w", err) diff --git a/pkg/plugins/external/init.go b/pkg/plugins/external/init.go index 64bfa21c08e..dccf978c3b5 100644 --- a/pkg/plugins/external/init.go +++ b/pkg/plugins/external/init.go @@ -32,14 +32,33 @@ type initSubcommand struct { Path string Args []string pluginChain []string + config config.Config } // InjectConfig injects the project configuration to access plugin chain information func (p *initSubcommand) InjectConfig(c config.Config) error { - p.pluginChain = c.GetPluginChain() + p.config = c + + if c == nil { + return nil + } + + if chain := c.GetPluginChain(); len(chain) > 0 { + p.pluginChain = append([]string(nil), chain...) + } + return nil } +func (p *initSubcommand) SetPluginChain(chain []string) { + if len(chain) == 0 { + p.pluginChain = nil + return + } + + p.pluginChain = append([]string(nil), chain...) +} + func (p *initSubcommand) UpdateMetadata(_ plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { setExternalPluginMetadata("init", p.Path, subcmdMeta) } @@ -56,7 +75,7 @@ func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error { PluginChain: p.pluginChain, } - err := handlePluginResponse(fs, req, p.Path) + err := handlePluginResponse(fs, req, p.Path, p.config) if err != nil { return err } diff --git a/pkg/plugins/external/webhook.go b/pkg/plugins/external/webhook.go index 377cd670fae..77c539c9f16 100644 --- a/pkg/plugins/external/webhook.go +++ b/pkg/plugins/external/webhook.go @@ -32,14 +32,33 @@ type createWebhookSubcommand struct { Path string Args []string pluginChain []string + config config.Config } -// InjectConfig injects the project configuration to access plugin chain information +// InjectConfig injects the project configuration so external plugins can read the PROJECT file. func (p *createWebhookSubcommand) InjectConfig(c config.Config) error { - p.pluginChain = c.GetPluginChain() + p.config = c + + if c == nil { + return nil + } + + if chain := c.GetPluginChain(); len(chain) > 0 { + p.pluginChain = append([]string(nil), chain...) + } + return nil } +func (p *createWebhookSubcommand) SetPluginChain(chain []string) { + if len(chain) == 0 { + p.pluginChain = nil + return + } + + p.pluginChain = append([]string(nil), chain...) +} + func (p *createWebhookSubcommand) InjectResource(*resource.Resource) error { // Do nothing since resource flags are passed to the external plugin directly. return nil @@ -61,7 +80,7 @@ func (p *createWebhookSubcommand) Scaffold(fs machinery.Filesystem) error { PluginChain: p.pluginChain, } - err := handlePluginResponse(fs, req, p.Path) + err := handlePluginResponse(fs, req, p.Path, p.config) if err != nil { return err }