github.com/kcmvp/gob@v1.0.17/cmd/gbc/artifact/plugin.go (about)

     1  package artifact
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/json"
     6  	"fmt"
     7  	"github.com/samber/lo"
     8  	"io/fs"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"regexp"
    13  	"strings"
    14  )
    15  
    16  const modulePattern = `^[^@]+@?[^@\s]+$`
    17  
    18  type Plugin struct {
    19  	Alias       string `json:"alias" mapstructure:"alias"`
    20  	Args        string `json:"args" mapstructure:"args"`
    21  	Url         string `json:"url" mapstructure:"url"` //nolint
    22  	Config      string `json:"load" mapstructure:"load"`
    23  	Description string `json:"description" mapstructure:"description"`
    24  	version     string
    25  	name        string
    26  	module      string
    27  }
    28  
    29  func (plugin *Plugin) init() error {
    30  	re := regexp.MustCompile(modulePattern)
    31  	if !re.MatchString(plugin.Url) {
    32  		return fmt.Errorf("invalud tool url %s", plugin.Url)
    33  	}
    34  	plugin.version = "latest"
    35  	plugin.Url = strings.TrimSpace(plugin.Url)
    36  	reg := regexp.MustCompile(`@\S*`)
    37  	matches := reg.FindAllString(plugin.Url, -1)
    38  	if len(matches) > 0 {
    39  		plugin.version = strings.Trim(matches[0], "@")
    40  	}
    41  	plugin.Url = reg.ReplaceAllString(plugin.Url, "")
    42  	plugin.module = plugin.Url
    43  	if strings.Contains(plugin.module, "github.com") {
    44  		segs := strings.Split(plugin.module, "/")
    45  		if len(segs) > 2 {
    46  			plugin.module = strings.Join(segs[0:3], "/")
    47  		}
    48  	}
    49  	plugin.name, _ = lo.Last(strings.Split(plugin.module, "/"))
    50  	if plugin.version == "latest" {
    51  		plugin.version = LatestVersion(plugin.module)[0].B
    52  	}
    53  	return nil
    54  }
    55  
    56  func LatestVersion(modules ...string) []lo.Tuple2[string, string] {
    57  	modules = lo.Map(modules, func(item string, _ int) string {
    58  		return fmt.Sprintf("%s@latest", item)
    59  	})
    60  	output, _ := exec.Command("go", append([]string{"list", "-m"}, modules...)...).CombinedOutput() //nolint
    61  	scanner := bufio.NewScanner(strings.NewReader(string(output)))
    62  	var tuple []lo.Tuple2[string, string]
    63  	for scanner.Scan() {
    64  		line := strings.TrimSpace(scanner.Text())
    65  		entry := strings.Split(line, " ")
    66  		tuple = append(tuple, lo.Tuple2[string, string]{A: entry[0], B: entry[1]})
    67  	}
    68  	return tuple
    69  }
    70  
    71  func (plugin Plugin) Module() string {
    72  	return plugin.module
    73  }
    74  
    75  func (plugin *Plugin) UnmarshalJSON(data []byte) error {
    76  	type Embedded Plugin
    77  	aux := &struct {
    78  		*Embedded
    79  	}{
    80  		(*Embedded)(plugin),
    81  	}
    82  	if err := json.Unmarshal(data, &aux); err != nil {
    83  		fmt.Println(err.Error())
    84  		return err
    85  	}
    86  	return (*Plugin)(aux.Embedded).init()
    87  }
    88  
    89  func NewPlugin(url string) (Plugin, error) {
    90  	plugin := Plugin{
    91  		Url: url,
    92  	}
    93  	if err := plugin.init(); err != nil {
    94  		return Plugin{}, err
    95  	}
    96  	return plugin, nil
    97  }
    98  func (plugin Plugin) Version() string {
    99  	return plugin.version
   100  }
   101  
   102  func (plugin Plugin) Name() string {
   103  	return plugin.name
   104  }
   105  
   106  func (plugin Plugin) taskName() string {
   107  	return lo.If(len(plugin.Alias) > 0, plugin.Alias).Else(plugin.Name())
   108  }
   109  
   110  func (plugin Plugin) Binary() string {
   111  	return lo.IfF(Windows(), func() string {
   112  		return fmt.Sprintf("%s-%s.exe", plugin.Name(), plugin.Version())
   113  	}).Else(fmt.Sprintf("%s-%s", plugin.Name(), plugin.Version()))
   114  }
   115  
   116  // install a plugin when it does not exist
   117  func (plugin Plugin) install() (string, error) {
   118  	gopath := GoPath()
   119  	if _, err := os.Stat(filepath.Join(gopath, plugin.Binary())); err == nil {
   120  		return "", nil
   121  	}
   122  	tempGoPath := temporaryGoPath()
   123  	defer os.RemoveAll(tempGoPath)
   124  	cmd := exec.Command("go", "install", fmt.Sprintf("%s@%s", plugin.Url, plugin.Version())) //nolint:gosec
   125  	cmd.Env = lo.Map(os.Environ(), func(pair string, _ int) string {
   126  		if strings.HasPrefix(pair, "GOPATH=") {
   127  			return fmt.Sprintf("%s=%s", "GOPATH", tempGoPath)
   128  		}
   129  		return pair
   130  	})
   131  	task := fmt.Sprintf("%s installation", plugin.Name())
   132  	if err := StreamCmdOutput(cmd, task, func(msg string) string {
   133  		return ""
   134  	}); err != nil {
   135  		return tempGoPath, err
   136  	}
   137  	if cmd.ProcessState.ExitCode() != 0 {
   138  		return tempGoPath, fmt.Errorf("faild %d", cmd.ProcessState.ExitCode())
   139  	}
   140  	return tempGoPath, filepath.WalkDir(tempGoPath, func(path string, d fs.DirEntry, err error) error {
   141  		if err != nil {
   142  			return err
   143  		}
   144  		if !d.IsDir() && strings.HasSuffix(filepath.Dir(path), "bin") && strings.HasPrefix(d.Name(), plugin.name) {
   145  			err = os.Rename(path, filepath.Join(gopath, plugin.Binary()))
   146  			if err != nil {
   147  				return err
   148  			}
   149  		} else {
   150  			// change the permission for deletion
   151  			os.Chmod(path, 0o766) //nolint
   152  		}
   153  		return err
   154  	})
   155  }
   156  
   157  func (plugin Plugin) Execute() error {
   158  	if _, err := plugin.install(); err != nil {
   159  		return err
   160  	}
   161  	// always use absolute path
   162  	pCmd := exec.Command(filepath.Join(GoPath(), plugin.Binary()), strings.Split(plugin.Args, " ")...) //nolint #gosec
   163  	if err := StreamCmdOutput(pCmd, plugin.taskName(), nil); err != nil {
   164  		return err
   165  	}
   166  	if pCmd.ProcessState.ExitCode() != 0 {
   167  		return fmt.Errorf("faild %d", pCmd.ProcessState.ExitCode())
   168  	}
   169  	return nil
   170  }