Skip to content

Commit 2e93b28

Browse files
authored
Merge pull request #2810 from everettraven/p2p/simple-external-plugin
🌱 (phase 2 plugins): add simple external plugin to testdata
2 parents 2f8bd85 + 9b172db commit 2e93b28

File tree

14 files changed

+750
-0
lines changed

14 files changed

+750
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
SHELL = /bin/bash
2+
3+
build:
4+
go build -o ./bin/sampleexternalplugin
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package cmd
17+
18+
import (
19+
"bufio"
20+
"encoding/json"
21+
"fmt"
22+
"io"
23+
"os"
24+
25+
"v1/scaffolds"
26+
27+
"sigs.k8s.io/kubebuilder/v3/pkg/plugin/external"
28+
)
29+
30+
// Run will run the actual steps of the plugin
31+
func Run() {
32+
// Phase 2 Plugins makes requests to an external plugin by
33+
// writing to the STDIN buffer. This means that an external plugin
34+
// call will NOT include any arguments other than the program name
35+
// itself. In order to get the request JSON from Kubebuilder
36+
// we will need to read the input from STDIN
37+
reader := bufio.NewReader(os.Stdin)
38+
39+
input, err := io.ReadAll(reader)
40+
if err != nil {
41+
returnError(fmt.Errorf("encountered error reading STDIN: %w", err))
42+
}
43+
44+
// Parse the JSON input from STDIN to a PluginRequest object.
45+
// Since the Phase 2 Plugin implementation was written in Go
46+
// there is already a Go API in place to represent these values.
47+
// Phase 2 Plugins can be written in any language, but you may
48+
// need to create some classes/interfaces to parse the JSON used
49+
// in the Phase 2 Plugins communication. More information on the
50+
// Phase 2 Plugin JSON schema can be found in the Phase 2 Plugins docs
51+
pluginRequest := &external.PluginRequest{}
52+
53+
err = json.Unmarshal(input, pluginRequest)
54+
if err != nil {
55+
returnError(fmt.Errorf("encountered error unmarshaling STDIN: %w", err))
56+
}
57+
58+
var response external.PluginResponse
59+
60+
// Run logic depending on the command that is requested by Kubebuilder
61+
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)
75+
// the `flags` subcommand is used to customize the flags that
76+
// the Kubebuilder cli will bind for use with this plugin
77+
case "flags":
78+
response = flagsCmd(pluginRequest)
79+
// the `metadata` subcommand is used to customize the
80+
// plugin metadata (help message and examples) that are
81+
// shown to Kubebuilder CLI users.
82+
case "metadata":
83+
response = metadataCmd(pluginRequest)
84+
// Any errors should still be returned as part of the plugin's
85+
// JSON response. There is an `error` boolean field to signal to
86+
// Kubebuilder that the external plugin encountered an error.
87+
// There is also an `errorMsgs` string array field to provide all
88+
// error messages to Kubebuilder.
89+
default:
90+
response = external.PluginResponse{
91+
Error: true,
92+
ErrorMsgs: []string{
93+
"unknown subcommand:" + pluginRequest.Command,
94+
},
95+
}
96+
}
97+
98+
// The Phase 2 Plugins implementation will read the response
99+
// from a Phase 2 Plugin via STDOUT. For Kubebuilder to properly
100+
// read our response we need to create a valid JSON string and
101+
// write it to the STDOUT buffer.
102+
output, err := json.Marshal(response)
103+
if err != nil {
104+
returnError(fmt.Errorf("encountered error marshaling output: %w | OUTPUT: %s", err, output))
105+
}
106+
107+
fmt.Printf("%s", output)
108+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package cmd
17+
18+
import (
19+
"v1/scaffolds"
20+
21+
"github.com/spf13/pflag"
22+
"sigs.k8s.io/kubebuilder/v3/pkg/plugin/external"
23+
)
24+
25+
// flagsCmd handles all the logic for the `flags` subcommand of the sample external plugin.
26+
// In Kubebuilder's Phase 2 Plugins the `flags` subcommand is an optional subcommand for
27+
// external plugins to support. The `flags` subcommand allows for an external plugin
28+
// to provide Kubebuilder with a list of flags that the `init`, `create api`, `create webhook`,
29+
// and `edit` subcommands allow. This allows Kubebuilder to give an external plugin the ability
30+
// to feel like a native Kubebuilder plugin to a Kubebuilder user by only binding the supported
31+
// flags and failing early if an unknown flag is provided.
32+
func flagsCmd(pr *external.PluginRequest) external.PluginResponse {
33+
pluginResponse := external.PluginResponse{
34+
APIVersion: "v1alpha1",
35+
Command: "flags",
36+
Universe: pr.Universe,
37+
Flags: []external.Flag{},
38+
}
39+
40+
// Here is an example of parsing multiple flags from a Kubebuilder external plugin request
41+
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")
45+
46+
flagsToParse.Parse(pr.Args)
47+
48+
initFlag, _ := flagsToParse.GetBool("init")
49+
apiFlag, _ := flagsToParse.GetBool("api")
50+
webhookFlag, _ := flagsToParse.GetBool("webhook")
51+
52+
// The Phase 2 Plugins implementation will only ever pass a single boolean flag
53+
// argument in the JSON request `args` field. The flag will be `--init` if it is
54+
// attempting to get the flags for the `init` subcommand, `--api` for `create api`,
55+
// `--webhook` for `create webhook`, and `--edit` for `edit`
56+
if initFlag {
57+
// Add a flag to the JSON response `flags` field that Kubebuilder reads
58+
// to ensure it binds to the flags given in the response.
59+
pluginResponse.Flags = scaffolds.InitFlags
60+
} else if apiFlag {
61+
pluginResponse.Flags = scaffolds.ApiFlags
62+
} else if webhookFlag {
63+
pluginResponse.Flags = scaffolds.WebhookFlags
64+
} else {
65+
pluginResponse.Error = true
66+
pluginResponse.ErrorMsgs = []string{
67+
"unrecognized flag",
68+
}
69+
}
70+
71+
return pluginResponse
72+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package cmd
17+
18+
import (
19+
"encoding/json"
20+
"fmt"
21+
"log"
22+
23+
"sigs.k8s.io/kubebuilder/v3/pkg/plugin/external"
24+
)
25+
26+
// returnError is a helper function to return a JSON
27+
// response that states an error has occurred along
28+
// with the error message to Kubebuilder. If this
29+
// function encounters an error printing the JSON
30+
// response it just prints a normal error message
31+
// and exits with a non-zero exit code. Kubebuilder
32+
// will detect that an error has occurred if there is
33+
// a non-zero exit code from the external plugin, but
34+
// it is recommended to return a JSON response that states
35+
// an error has occurred to provide the best user experience
36+
// and integration with Kubebuilder.
37+
func returnError(err error) {
38+
errResponse := external.PluginResponse{
39+
Error: true,
40+
ErrorMsgs: []string{
41+
err.Error(),
42+
},
43+
}
44+
output, err := json.Marshal(errResponse)
45+
if err != nil {
46+
log.Fatalf("encountered error marshaling output: %s | OUTPUT: %s", err.Error(), output)
47+
}
48+
49+
fmt.Printf("%s", output)
50+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package cmd
17+
18+
import (
19+
"v1/scaffolds"
20+
21+
"github.com/spf13/pflag"
22+
"sigs.k8s.io/kubebuilder/v3/pkg/plugin/external"
23+
)
24+
25+
// metadataCmd handles all the logic for the `metadata` subcommand of the sample external plugin.
26+
// In Kubebuilder's Phase 2 Plugins the `metadata` subcommand is an optional subcommand for
27+
// external plugins to support. The `metadata` subcommand allows for an external plugin
28+
// to provide Kubebuilder with a description of the plugin and examples for each of the
29+
// `init`, `create api`, `create webhook`, and `edit` subcommands. This allows Kubebuilder
30+
// to provide users a native Kubebuilder plugin look and feel for an external plugin.
31+
func metadataCmd(pr *external.PluginRequest) external.PluginResponse {
32+
pluginResponse := external.PluginResponse{
33+
APIVersion: "v1alpha1",
34+
Command: "flags",
35+
Universe: pr.Universe,
36+
}
37+
38+
// Here is an example of parsing multiple flags from a Kubebuilder external plugin request
39+
flagsToParse := pflag.NewFlagSet("flagsFlags", pflag.ContinueOnError)
40+
flagsToParse.Bool("init", false, "sets the init flag to true")
41+
flagsToParse.Bool("api", false, "sets the api flag to true")
42+
flagsToParse.Bool("webhook", false, "sets the webhook flag to true")
43+
44+
flagsToParse.Parse(pr.Args)
45+
46+
initFlag, _ := flagsToParse.GetBool("init")
47+
apiFlag, _ := flagsToParse.GetBool("api")
48+
webhookFlag, _ := flagsToParse.GetBool("webhook")
49+
50+
// The Phase 2 Plugins implementation will only ever pass a single boolean flag
51+
// argument in the JSON request `args` field. The flag will be `--init` if it is
52+
// attempting to get the flags for the `init` subcommand, `--api` for `create api`,
53+
// `--webhook` for `create webhook`, and `--edit` for `edit`
54+
if initFlag {
55+
// Populate the JSON response `metadata` field with a description
56+
// and examples for the `init` subcommand
57+
pluginResponse.Metadata = scaffolds.InitMeta
58+
} else if apiFlag {
59+
pluginResponse.Metadata = scaffolds.ApiMeta
60+
} else if webhookFlag {
61+
pluginResponse.Metadata = scaffolds.WebhookMeta
62+
} else {
63+
pluginResponse.Error = true
64+
pluginResponse.ErrorMsgs = []string{
65+
"unrecognized flag",
66+
}
67+
}
68+
69+
return pluginResponse
70+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module v1
2+
3+
go 1.18
4+
5+
require (
6+
github.com/spf13/pflag v1.0.5
7+
sigs.k8s.io/kubebuilder/v3 v3.5.1-0.20220707134334-33ad938a65be
8+
)
9+
10+
require (
11+
github.com/gobuffalo/flect v0.2.5 // indirect
12+
github.com/spf13/afero v1.6.0 // indirect
13+
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
14+
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
15+
golang.org/x/text v0.3.7 // indirect
16+
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717 // indirect
17+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
18+
)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/gobuffalo/flect v0.2.5 h1:H6vvsv2an0lalEaCDRThvtBfmg44W/QHXBCYUXf/6S4=
3+
github.com/gobuffalo/flect v0.2.5/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8=
4+
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
5+
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
6+
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
7+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
8+
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
9+
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
10+
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
11+
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
12+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
13+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
14+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
15+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
16+
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
17+
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
18+
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
19+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
20+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
21+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
22+
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
23+
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
24+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
25+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
26+
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
27+
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
28+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
29+
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717 h1:hI3jKY4Hpf63ns040onEbB3dAkR/H/P83hw1TG8dD3Y=
30+
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
31+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
32+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
33+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
34+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
35+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
36+
sigs.k8s.io/kubebuilder/v3 v3.5.1-0.20220707134334-33ad938a65be h1:WZfqg8PpNxs094DKKq2dhcAFUZj/A628521NhLmLayE=
37+
sigs.k8s.io/kubebuilder/v3 v3.5.1-0.20220707134334-33ad938a65be/go.mod h1:+s6WdvJjIpYRKO+idaeIK5JhbjrZybxh9+K6jK9/Yyc=
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import "v1/cmd"
20+
21+
func main() {
22+
cmd.Run()
23+
}

0 commit comments

Comments
 (0)