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 }