Skip to content

Commit 63b82e0

Browse files
committed
✨ Refactor sampleexternalplugin to be a valid reference implementation
Transform the sample external plugin from mock scaffolding to a realistic working example that demonstrates best practices for external plugin development. Changes: - Replace mock init/create api/webhook commands with meaningful edit command - Add Prometheus ServiceMonitor scaffolding for operator monitoring - Implement PROJECT config reading to align with internal plugin patterns - Add local source replace directive for contributor testing - Update documentation to reflect realistic plugin usage The plugin now serves as a proper reference for: - Reading PROJECT config in external plugins - Scaffolding real, production-ready configurations - Testing plugins against local Kubebuilder source - Adding optional features via the edit subcommand Fixes #4824
1 parent eb935e8 commit 63b82e0

File tree

15 files changed

+325
-522
lines changed

15 files changed

+325
-522
lines changed

docs/book/src/plugins/extending/external-plugins.md

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ structures.
3030

3131
`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`.
3232

33-
**Example `PluginRequest` (triggered by `kubebuilder init --plugins sampleexternalplugin/v1 --domain my.domain`):**
33+
**Example `PluginRequest` (triggered by `kubebuilder edit --plugins sampleexternalplugin/v1`):**
3434

3535
```json
3636
{
3737
"apiVersion": "v1alpha1",
38-
"args": ["--domain", "my.domain"],
39-
"command": "init",
38+
"args": [],
39+
"command": "edit",
4040
"universe": {}
4141
}
4242
```
@@ -49,13 +49,14 @@ structures.
4949
```json
5050
{
5151
"apiVersion": "v1alpha1",
52-
"command": "init",
52+
"command": "edit",
5353
"metadata": {
54-
"description": "The `init` subcommand initializes a project via Kubebuilder. It scaffolds a single file: `initFile`.",
55-
"examples": "kubebuilder init --plugins sampleexternalplugin/v1 --domain my.domain"
54+
"description": "The `edit` subcommand adds Prometheus ServiceMonitor configuration for monitoring your operator.",
55+
"examples": "kubebuilder edit --plugins sampleexternalplugin/v1"
5656
},
5757
"universe": {
58-
"initFile": "A file created with the `init` subcommand."
58+
"config/prometheus/monitor.yaml": "# Prometheus ServiceMonitor manifest...",
59+
"config/prometheus/kustomization.yaml": "resources:\n - monitor.yaml\n"
5960
},
6061
"error": false,
6162
"errorMsgs": []
@@ -120,26 +121,15 @@ Otherwise, Kubebuilder would search for the plugins in a default path based on y
120121
You can now use it by calling the CLI commands:
121122

122123
```sh
123-
# Initialize a new project with the external plugin named `sampleplugin`
124-
kubebuilder init --plugins sampleplugin/v1
124+
# Update the project configuration with the sample external plugin
125+
# The sampleexternalplugin adds Prometheus ServiceMonitor configuration
126+
kubebuilder edit --plugins sampleexternalplugin/v1
125127

126-
# Display help information of the `init` subcommand of the external plugin
127-
kubebuilder init --plugins sampleplugin/v1 --help
128+
# Display help information for the edit subcommand
129+
kubebuilder edit --plugins sampleexternalplugin/v1 --help
128130

129-
# Create a new API with the above external plugin with a customized flag `number`
130-
kubebuilder create api --plugins sampleplugin/v1 --number 2
131-
132-
# Create a webhook with the above external plugin with a customized flag `hooked`
133-
kubebuilder create webhook --plugins sampleplugin/v1 --hooked
134-
135-
# Update the project configuration with the above external plugin
136-
kubebuilder edit --plugins sampleplugin/v1
137-
138-
# Create new APIs with external plugins v1 and v2 by respecting the plugin chaining order
139-
kubebuilder create api --plugins sampleplugin/v1,sampleplugin/v2
140-
141-
# Create new APIs with the go/v4 plugin and then pass those files to the external plugin by respecting the plugin chaining order
142-
kubebuilder create api --plugins go/v4,sampleplugin/v1
131+
# Plugin chaining example: Use go/v4 plugin first, then apply external plugin
132+
kubebuilder edit --plugins go/v4,sampleexternalplugin/v1
143133
```
144134

145135
## Further resources

docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/cmd/cmd.go

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,10 @@ func Run() {
5959

6060
// Run logic depending on the command that is requested by Kubebuilder
6161
switch pluginRequest.Command {
62-
// the `init` subcommand is often used when initializing a new project
63-
case "init":
64-
response = scaffolds.InitCmd(pluginRequest)
65-
// the `create api` subcommand is often used after initializing a project
66-
// with the `init` subcommand to create a controller and CRDs for a
67-
// provided group, version, and kind
68-
case "create api":
69-
response = scaffolds.ApiCmd(pluginRequest)
70-
// the `create webhook` subcommand is often used after initializing a project
71-
// with the `init` subcommand to create a webhook for a provided
72-
// group, version, and kind
73-
case "create webhook":
74-
response = scaffolds.WebhookCmd(pluginRequest)
62+
// the `edit` subcommand is used to add optional features to an existing project
63+
// This is a realistic use case for external plugins - adding optional monitoring
64+
case "edit":
65+
response = scaffolds.EditCmd(pluginRequest)
7566
// the `flags` subcommand is used to customize the flags that
7667
// the Kubebuilder cli will bind for use with this plugin
7768
case "flags":

docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/cmd/flags.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,8 @@ func flagsCmd(pr *external.PluginRequest) external.PluginResponse {
3737
}
3838

3939
switch pr.Command {
40-
case "init":
41-
pluginResponse.Flags = scaffolds.InitFlags
42-
case "create api":
43-
pluginResponse.Flags = scaffolds.ApiFlags
44-
case "create webhook":
45-
pluginResponse.Flags = scaffolds.WebhookFlags
40+
case "edit":
41+
pluginResponse.Flags = scaffolds.EditFlags
4642
default:
4743
pluginResponse.Error = true
4844
pluginResponse.ErrorMsgs = []string{

docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/cmd/metadata.go

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,7 @@ func metadataCmd(pr *external.PluginRequest) external.PluginResponse {
3939

4040
// Here is an example of parsing multiple flags from a Kubebuilder external plugin request
4141
flagsToParse := pflag.NewFlagSet("flagsFlags", pflag.ContinueOnError)
42-
flagsToParse.Bool("init", false, "sets the init flag to true")
43-
flagsToParse.Bool("api", false, "sets the api flag to true")
44-
flagsToParse.Bool("webhook", false, "sets the webhook flag to true")
42+
flagsToParse.Bool("edit", false, "sets the edit flag to true")
4543

4644
if err := flagsToParse.Parse(pr.Args); err != nil {
4745
pluginResponse.Error = true
@@ -51,22 +49,14 @@ func metadataCmd(pr *external.PluginRequest) external.PluginResponse {
5149
return pluginResponse
5250
}
5351

54-
initFlag, _ := flagsToParse.GetBool("init")
55-
apiFlag, _ := flagsToParse.GetBool("api")
56-
webhookFlag, _ := flagsToParse.GetBool("webhook")
52+
editFlag, _ := flagsToParse.GetBool("edit")
5753

5854
// The Phase 2 Plugins implementation will only ever pass a single boolean flag
59-
// argument in the JSON request `args` field. The flag will be `--init` if it is
60-
// attempting to get the flags for the `init` subcommand, `--api` for `create api`,
61-
// `--webhook` for `create webhook`, and `--edit` for `edit`
62-
if initFlag {
55+
// argument in the JSON request `args` field. The flag will be `--edit` for `edit`
56+
if editFlag {
6357
// Populate the JSON response `metadata` field with a description
64-
// and examples for the `init` subcommand
65-
pluginResponse.Metadata = scaffolds.InitMeta
66-
} else if apiFlag {
67-
pluginResponse.Metadata = scaffolds.ApiMeta
68-
} else if webhookFlag {
69-
pluginResponse.Metadata = scaffolds.WebhookMeta
58+
// and examples for the `edit` subcommand
59+
pluginResponse.Metadata = scaffolds.EditMeta
7060
} else {
7161
pluginResponse.Error = true
7262
pluginResponse.ErrorMsgs = []string{

docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@ go 1.24.5
55
require (
66
github.com/spf13/pflag v1.0.10
77
sigs.k8s.io/kubebuilder/v4 v4.9.0
8+
sigs.k8s.io/yaml v1.6.0
89
)
910

11+
replace sigs.k8s.io/kubebuilder/v4 => ../../../../../../../
12+
1013
require (
1114
github.com/gobuffalo/flect v1.0.3 // indirect
15+
github.com/kr/text v0.2.0 // indirect
1216
github.com/spf13/afero v1.15.0 // indirect
17+
go.yaml.in/yaml/v2 v2.4.2 // indirect
1318
golang.org/x/mod v0.28.0 // indirect
1419
golang.org/x/sync v0.17.0 // indirect
1520
golang.org/x/text v0.29.0 // indirect

docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/go.sum

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
22
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
3+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
34
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
45
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
56
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -13,12 +14,18 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
1314
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
1415
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
1516
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
17+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
18+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
19+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
20+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
1621
github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw=
1722
github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE=
1823
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
1924
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
2025
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
2126
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
27+
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
28+
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
2229
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
2330
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
2431
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
@@ -49,10 +56,10 @@ golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
4956
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
5057
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
5158
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
59+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
60+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
5261
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
5362
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
5463
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
55-
sigs.k8s.io/kubebuilder/v4 v4.9.0 h1:9e9LnQy/wQ24IZDIqye6iZZFOB9aKNyNfjnfsy3S8cw=
56-
sigs.k8s.io/kubebuilder/v4 v4.9.0/go.mod h1:Xql7wLeyXBQ4lJJdi1Pl8T/DeV4UXpA1kaOEumN0pzY=
5764
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
5865
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/scaffolds/api.go

Lines changed: 0 additions & 116 deletions
This file was deleted.

0 commit comments

Comments
 (0)