client/tools/knb/pkg/plugin/distro.go

167 lines
4.9 KiB
Go

/*
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)
}