mirror of https://github.com/knative/client.git
feat: Add knb plugin build tool (#1226)
* feat: Add knb plugin build tool * fix: Fix lint errors * fix: Fix trailing whitespaces in readme * fix: Fix error msg * fix: Fix all error msg * fix: Move knb to sub-dir tools/knb * fix: Add new dir to sources * fix: Reflect review feedback * fix: Reflect review feedback in readme * fix: Code formatting * fix: Fix gosec errors
This commit is contained in:
parent
d49194648b
commit
d44f25d350
|
@ -16,6 +16,8 @@ cover.html
|
|||
/kn
|
||||
/kn-*
|
||||
|
||||
tools/knb/knb
|
||||
|
||||
# emacs tempfiles
|
||||
\#*
|
||||
*~
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
set -o pipefail
|
||||
|
||||
source_dirs="cmd pkg test lib"
|
||||
source_dirs="cmd pkg test lib tools"
|
||||
|
||||
# Store for later
|
||||
if [ -z "$1" ]; then
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
# knb - kn builder
|
||||
|
||||
CLI tool to enhance plugin build experience for [Knative Client](https://github.com/knative/client).
|
||||
|
||||
|
||||
### Build
|
||||
```bash
|
||||
go build
|
||||
```
|
||||
|
||||
### Install
|
||||
```bash
|
||||
go get knative.dev/client/tools/knb
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
##### Create custom `kn` distribution
|
||||
|
||||
The `knb` can be used to generate enhanced customized `kn` source files with inlined plugins.
|
||||
|
||||
Create configuration file `.kn.yaml` in a root directory of `knative/client` that should specify at least `name, module, version` coordinates of the plugin.
|
||||
Executing `knb plugin distro` command will generate the required go files and add dependency to `go.mod`.
|
||||
|
||||
Example of `.kn.yaml`
|
||||
```yaml
|
||||
plugins:
|
||||
- name: kn-plugin-source-kafka
|
||||
module: knative.dev/kn-plugin-source-kafka
|
||||
pluginImportPath: knative.dev/kn-plugin-source-kafka/plugin
|
||||
version: v0.19.0
|
||||
replace:
|
||||
- module: golang.org/x/sys
|
||||
version: v0.0.0-20200302150141-5c8b2ff67527
|
||||
```
|
||||
|
||||
Required:
|
||||
* name
|
||||
* module - go module name to be used for import and in go.mod file
|
||||
* version - accepted values are git tag or branch name of go module.
|
||||
|
||||
Optional:
|
||||
* pluginImportPath - import path override, default `$module/plugin`
|
||||
* replace - go module replacement defined by `module,version`.
|
||||
|
||||
|
||||
Execute command
|
||||
```bash
|
||||
knb plugin distro
|
||||
```
|
||||
|
||||
|
||||
Build `kn`
|
||||
```bash
|
||||
./hack/build.sh
|
||||
```
|
||||
|
||||
##### Enable plugin inline feature
|
||||
|
||||
The `knb` can be used to generate required go files to inline any `kn` plugin.
|
||||
|
||||
```bash
|
||||
knb plugin init --name kn-source-kafka --cmd source,kafka --description "Some plugin"
|
||||
```
|
||||
|
||||
|
||||
##### List of commands
|
||||
|
||||
Plugin level commands
|
||||
|
||||
```
|
||||
Manage kn plugins.
|
||||
|
||||
Usage:
|
||||
knb plugin [command]
|
||||
|
||||
Available Commands:
|
||||
distro Generate required files to build `kn` with inline plugins.
|
||||
init Generate required resource to inline plugin.
|
||||
|
||||
Flags:
|
||||
-h, --help help for plugin
|
||||
|
||||
Use "knb plugin [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
```
|
||||
Generate required files to build `kn` with inline plugins.
|
||||
|
||||
Usage:
|
||||
knb plugin distro [flags]
|
||||
|
||||
Flags:
|
||||
-c, --config kn.yaml Path to kn.yaml config file (default ".kn.yaml")
|
||||
-h, --help help for distro
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
Generate required resource to inline plugin.
|
||||
|
||||
Usage:
|
||||
knb plugin init [flags]
|
||||
|
||||
Flags:
|
||||
--cmd kn service log Defines command parts to execute plugin from kn. E.g kn service log can be achieved with `--cmd service,log`.
|
||||
--description string Description of a plugin.
|
||||
-h, --help help for init
|
||||
--import string Import path of plugin.
|
||||
--name string Name of a plugin.
|
||||
--output-dir string Output directory to write plugin.go file. (default "plugin")
|
||||
```
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
Copyright 2021 The Knative 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"knative.dev/client/tools/knb/pkg/root"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := root.NewKnBuilderCmd().Execute(); err != nil {
|
||||
fmt.Printf("ERROR: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
Copyright 2021 The Knative 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 plugin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// DistroConfig represents yaml configuration struct
|
||||
type DistroConfig struct {
|
||||
Plugins []Plugin `yaml:"plugins"`
|
||||
}
|
||||
|
||||
// NewDistroGenerateCmd represents plugin distro command
|
||||
func NewDistroGenerateCmd() *cobra.Command {
|
||||
var config string
|
||||
var generateCmd = &cobra.Command{
|
||||
Use: "distro",
|
||||
Short: "Generate required files to build `kn` with inline plugins.",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if !fileExists(config) {
|
||||
return fmt.Errorf("kn distro configuration file '%s' doesn't exist", config)
|
||||
}
|
||||
if !fileExists("cmd/kn/main.go") {
|
||||
return fmt.Errorf("cmd/kn/main.go doesn't exist, make sure the command is executed in knative/client root directory")
|
||||
}
|
||||
|
||||
fmt.Println("Generating customized kn distro:")
|
||||
registerFile := filepath.Join("pkg", "kn", "root", "plugin_register.go")
|
||||
if fileExists(registerFile) {
|
||||
fmt.Println("⚠️ plugin_register.go file already exists, trying to append imports")
|
||||
}
|
||||
|
||||
rawConf, err := ioutil.ReadFile(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conf := &DistroConfig{}
|
||||
if err := yaml.Unmarshal(rawConf, conf); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("✔ config file '" + config + "' processed")
|
||||
|
||||
for _, p := range conf.Plugins {
|
||||
if err := processPlugin(p, registerFile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
//nolint:gosec // Expected go cmd.
|
||||
if err := exec.Command("gofmt", "-s", "-w", registerFile).Run(); err != nil {
|
||||
return fmt.Errorf("gofmt failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
generateCmd.Flags().StringVarP(&config, "config", "c", ".kn.yaml", "Path to `kn.yaml` config file")
|
||||
return generateCmd
|
||||
}
|
||||
|
||||
// fileExists util func to check if file exists
|
||||
func fileExists(file string) bool {
|
||||
_, err := os.Stat(file)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// processPlugin does required import changes in plugin_register.go and go.mod file
|
||||
func processPlugin(p Plugin, registerFile string) error {
|
||||
importPath := p.PluginImportPath
|
||||
if importPath == "" {
|
||||
importPath = p.Module + "/plugin"
|
||||
}
|
||||
if err := appendImport(registerFile, importPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := processModuleRequire(p); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := processModuleReplace(p); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// processModuleRequire adds provided plugin module to go.mod require section
|
||||
func processModuleRequire(plugin Plugin) error {
|
||||
//nolint:gosec // Expected go cmd.
|
||||
_, err := exec.Command("go", "mod", "edit", "-require", plugin.Module+"@"+plugin.Version).Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("go mod edit -require failed: %w", err)
|
||||
}
|
||||
fmt.Println("✔ go.mod require updated")
|
||||
return nil
|
||||
}
|
||||
|
||||
// processModuleReplace adds provided module overrides to go.mod replace section
|
||||
func processModuleReplace(plugin Plugin) error {
|
||||
if len(plugin.Replace) > 0 {
|
||||
for _, r := range plugin.Replace {
|
||||
//nolint:gosec // Expected go cmd.
|
||||
_, err := exec.Command("go", "mod", "edit", "-replace", r.Module+"="+r.Module+"@"+r.Version).Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("go mod edit -replace failed: %w", err)
|
||||
}
|
||||
fmt.Println("✔ go.mod replace updated")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// appendImport adds specified importPath to plugin registration file.
|
||||
// New file is initialized if it doesn't exist.
|
||||
// Warning message is displayed if the plugin import is already present.
|
||||
func appendImport(file, importPath string) error {
|
||||
if _, err := os.Stat(file); os.IsNotExist(err) {
|
||||
f, err := os.Create(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := template.New("register").Parse(registerTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("✔ " + importPath + " added to plugin_register.go")
|
||||
return t.Execute(f, importPath)
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.Contains(string(content), importPath) {
|
||||
fmt.Println("⚠️ " + importPath + " is already present, no changes made")
|
||||
return nil
|
||||
}
|
||||
hook := "// Add #plugins# import here. Don't remove this line, it triggers an automatic replacement."
|
||||
content = bytes.Replace(content, []byte(hook), []byte(fmt.Sprintf("%s\n _ \"%s\"", hook, importPath)), 1)
|
||||
fmt.Println("✔ " + importPath + " added to plugin_register.go")
|
||||
//nolint:gosec // Generate file keeps the same permissions as rest of sources.
|
||||
return ioutil.WriteFile(file, content, 0644)
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
Copyright 2021 The Knative 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 plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewPluginInitCmd represents plugin init command
|
||||
func NewPluginInitCmd() *cobra.Command {
|
||||
plugin := &Plugin{}
|
||||
var outputDir string
|
||||
|
||||
var registerCmd = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Generate required resource to inline plugin.",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := os.MkdirAll(outputDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outputFile := filepath.Join(outputDir, "plugin.go")
|
||||
if _, err := os.Stat(outputFile); err == nil {
|
||||
return fmt.Errorf("file '%s' already exists", outputFile)
|
||||
}
|
||||
fmt.Println("Generating plugin inline file:")
|
||||
f, err := os.Create(outputFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := template.New("init").Parse(pluginTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.Execute(f, plugin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("✔ plugin inline file generated " + outputFile)
|
||||
err = f.Close()
|
||||
return err
|
||||
},
|
||||
}
|
||||
registerCmd.Flags().StringVar(&plugin.Name, "name", "", "Name of a plugin.")
|
||||
registerCmd.Flags().StringVar(&plugin.Description, "description", "", "Description of a plugin.")
|
||||
registerCmd.Flags().StringVar(&plugin.PluginImportPath, "import", "", "Import path of plugin.")
|
||||
registerCmd.Flags().StringVar(&outputDir, "output-dir", "plugin", "Output directory to write plugin.go file.")
|
||||
registerCmd.Flags().StringSliceVar(&plugin.CmdParts, "cmd", []string{}, "Defines command parts to execute plugin from kn. "+
|
||||
"E.g. `kn service log` can be achieved with `--cmd service,log`.")
|
||||
|
||||
return registerCmd
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
Copyright 2021 The Knative 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 plugin
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
// Plugin represents plugin configuration data struct
|
||||
type Plugin struct {
|
||||
Name string `yaml:"name"`
|
||||
Description string `yaml:"description,omitempty"`
|
||||
Module string `yaml:"module"`
|
||||
Version string `yaml:"version"`
|
||||
PluginImportPath string `yaml:"pluginImportPath,omitempty"`
|
||||
CmdParts []string `yaml:"cmdParts,omitempty"`
|
||||
Replace []Replace `yaml:"replace,omitempty"`
|
||||
}
|
||||
|
||||
// Plugin represents go module replacement declaration
|
||||
type Replace struct {
|
||||
Module string `yaml:"module"`
|
||||
Version string `yaml:"version"`
|
||||
}
|
||||
|
||||
// NewPluginCmd represents plugin command group
|
||||
func NewPluginCmd() *cobra.Command {
|
||||
var pluginCmd = &cobra.Command{
|
||||
Use: "plugin",
|
||||
Short: "Manage kn plugins.",
|
||||
}
|
||||
pluginCmd.AddCommand(NewPluginInitCmd())
|
||||
pluginCmd.AddCommand(NewDistroGenerateCmd())
|
||||
return pluginCmd
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
Copyright 2021 The Knative 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 plugin
|
||||
|
||||
const registerTemplate = `
|
||||
/*
|
||||
Copyright 2021 The Knative 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 root
|
||||
|
||||
import (
|
||||
// Add #plugins# import here. Don't remove this line, it triggers an automatic replacement.
|
||||
_ "{{.}}"
|
||||
)
|
||||
|
||||
// RegisterInlinePlugins is an empty function which however forces the
|
||||
// compiler to run all init() methods of the registered imports
|
||||
func RegisterInlinePlugins() {}`
|
||||
|
||||
const pluginTemplate = `
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
{{if .PluginImportPath}}"{{.PluginImportPath }}"{{else}}//TODO: add plugin import{{end}}
|
||||
|
||||
"knative.dev/client/pkg/kn/plugin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
plugin.InternalPlugins = append(plugin.InternalPlugins, &inlinedPlugin{})
|
||||
}
|
||||
|
||||
type inlinedPlugin struct{}
|
||||
|
||||
// Name is a plugin's name
|
||||
func (p *inlinedPlugin) Name() string {
|
||||
return "{{.Name}}"
|
||||
}
|
||||
|
||||
// Execute represents the plugin's entrypoint when called through kn
|
||||
func (p *inlinedPlugin) Execute(args []string) error {
|
||||
//TODO: implement plugin command execution
|
||||
//cmd := root.NewPluginCommand()
|
||||
//oldArgs := os.Args
|
||||
//defer (func() {
|
||||
// os.Args = oldArgs
|
||||
//})()
|
||||
//os.Args = append([]string{"{{.Name}}"}, args...)
|
||||
//return cmd.Execute()
|
||||
return errors.New("plugin execution is not implemented yet")
|
||||
}
|
||||
|
||||
// Description is displayed in kn's plugin section
|
||||
func (p *inlinedPlugin) Description() (string, error) {
|
||||
{{if .Description}}return "{{.Description}}", nil{{else}}//TODO: add description
|
||||
return "", nil{{end}}
|
||||
}
|
||||
|
||||
// CommandParts defines for plugin is executed from kn
|
||||
func (p *inlinedPlugin) CommandParts() []string {
|
||||
return []string{ {{- range $i,$v := .CmdParts}}{{if $i}}, {{end}}"{{.}}"{{end -}} }
|
||||
}
|
||||
|
||||
// Path is empty because its an internal plugins
|
||||
func (p *inlinedPlugin) Path() string {
|
||||
return ""
|
||||
}`
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
Copyright 2021 The Knative 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 root
|
||||
|
||||
import (
|
||||
"knative.dev/client/tools/knb/pkg/plugin"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewKnBuilderCmd represent root command level
|
||||
func NewKnBuilderCmd() *cobra.Command {
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "knb",
|
||||
Short: "Manage and build kn inline plugins.",
|
||||
SilenceErrors: true,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
rootCmd.AddCommand(plugin.NewPluginCmd())
|
||||
return rootCmd
|
||||
}
|
Loading…
Reference in New Issue