Skip to content

Commit

Permalink
feat: create plugin resolve system
Browse files Browse the repository at this point in the history
  • Loading branch information
christophwitzko committed Jun 13, 2022
1 parent 4afe3b1 commit f61374a
Show file tree
Hide file tree
Showing 12 changed files with 310 additions and 161 deletions.
22 changes: 10 additions & 12 deletions pkg/plugin/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ import (
"github.com/hashicorp/go-plugin"
)

type PluginOpts struct {
Type string
PluginName string
Cmd *exec.Cmd
}

var runningClientsMx sync.Mutex
var runningClients = make([]*plugin.Client, 0)

Expand All @@ -31,11 +25,11 @@ func KillAllPlugins() {
}
}

func StartPlugin(opts *PluginOpts) (interface{}, error) {
func StartPlugin(pluginInfo *PluginInfo) (interface{}, error) {
runningClientsMx.Lock()
defer runningClientsMx.Unlock()
logR, logW := io.Pipe()
pluginLogger := log.New(os.Stderr, fmt.Sprintf("[%s]: ", opts.PluginName), 0)
pluginLogger := log.New(os.Stderr, fmt.Sprintf("[%s]: ", pluginInfo.NormalizedName), 0)
go func() {
logLineScanner := bufio.NewScanner(logR)
for logLineScanner.Scan() {
Expand All @@ -47,16 +41,20 @@ func StartPlugin(opts *PluginOpts) (interface{}, error) {
pluginLogger.Println(line)
}
}()

cmd := exec.Command(pluginInfo.BinPath)
cmd.SysProcAttr = GetSysProcAttr()

client := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: Handshake,
VersionedPlugins: map[int]plugin.PluginSet{
1: {
opts.Type: &GRPCWrapper{
Type: opts.Type,
pluginInfo.Type: &GRPCWrapper{
Type: pluginInfo.Type,
},
},
},
Cmd: opts.Cmd,
Cmd: cmd,
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
Logger: hclog.NewNullLogger(),
Stderr: logW,
Expand All @@ -67,7 +65,7 @@ func StartPlugin(opts *PluginOpts) (interface{}, error) {
client.Kill()
return nil, err
}
raw, err := rpcClient.Dispense(opts.Type)
raw, err := rpcClient.Dispense(pluginInfo.Type)
if err != nil {
client.Kill()
return nil, err
Expand Down
90 changes: 35 additions & 55 deletions pkg/plugin/discovery/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,82 +2,62 @@ package discovery

import (
"errors"
"os/exec"
"strings"
"fmt"

"github.com/Masterminds/semver/v3"
"github.com/go-semantic-release/semantic-release/v2/pkg/analyzer"
"github.com/go-semantic-release/semantic-release/v2/pkg/condition"
"github.com/go-semantic-release/semantic-release/v2/pkg/config"
"github.com/go-semantic-release/semantic-release/v2/pkg/generator"
"github.com/go-semantic-release/semantic-release/v2/pkg/hooks"
"github.com/go-semantic-release/semantic-release/v2/pkg/plugin"
"github.com/go-semantic-release/semantic-release/v2/pkg/provider"
"github.com/go-semantic-release/semantic-release/v2/pkg/updater"
"github.com/go-semantic-release/semantic-release/v2/pkg/plugin/discovery/resolver"
"github.com/go-semantic-release/semantic-release/v2/pkg/plugin/discovery/resolver/registry"
)

type Discovery struct {
config *config.Config
config *config.Config
resolvers map[string]resolver.Resolver
}

func New(config *config.Config) (*Discovery, error) {
return &Discovery{config}, nil
registryResolver := registry.NewResolver()
return &Discovery{
config: config,
resolvers: map[string]resolver.Resolver{
"default": registryResolver,
"registry": registryResolver,
},
}, nil
}

func getPluginType(t string) string {
switch t {
case analyzer.CommitAnalyzerPluginName:
return "commit-analyzer"
case condition.CIConditionPluginName:
return "condition"
case generator.ChangelogGeneratorPluginName:
return "changelog-generator"
case provider.PluginName:
return "provider"
case updater.FilesUpdaterPluginName:
return "files-updater"
case hooks.PluginName:
return "hooks"
func (d *Discovery) fetchPlugin(pluginInfo *plugin.PluginInfo) (string, error) {
pluginResolver, ok := d.resolvers[pluginInfo.Resolver]
if !ok {
return "", fmt.Errorf("resolver %s not found", pluginInfo.Resolver)
}
return ""
}

func (d *Discovery) FindPlugin(t, name string) (*plugin.PluginOpts, error) {
pType := getPluginType(t)
if pType == "" {
return nil, errors.New("invalid plugin type")
downloadInfo, err := pluginResolver.ResolvePlugin(pluginInfo)
if err != nil {
return "", err
}

var cons *semver.Constraints
if ve := strings.SplitN(name, "@", 2); len(ve) > 1 {
v, err := semver.NewConstraint(ve[1])
if err != nil {
return nil, err
}
name = ve[0]
cons = v
}
return downloadPlugin(pluginInfo, downloadInfo, d.config.ShowProgress)
}

pName := strings.ToLower(pType + "-" + name)
pPath := getPluginPath(pName)
if err := ensurePluginDir(pPath); err != nil {
func (d *Discovery) FindPlugin(t, name string) (*plugin.PluginInfo, error) {
pInfo, err := plugin.GetPluginInfo(t, name)
if err != nil {
return nil, err
}
if err := setAndEnsurePluginPath(pInfo); err != nil {
return nil, err
}

binPath, err := findPluginLocally(pPath, cons)
if err != nil {
binPath, err = fetchPlugin(pName, pPath, cons, d.config.ShowProgress)
binPath, err := findPluginLocally(pInfo)
if errors.Is(err, ErrPluginNotFound) {
binPath, err = d.fetchPlugin(pInfo)
if err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}

cmd := exec.Command(binPath)
cmd.SysProcAttr = GetSysProcAttr()

return &plugin.PluginOpts{
Type: t,
PluginName: pName,
Cmd: cmd,
}, nil
pInfo.BinPath = binPath
return pInfo, nil
}
58 changes: 8 additions & 50 deletions pkg/plugin/discovery/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ package discovery
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"os"
"path"
"runtime"
"sort"
"time"

"github.com/Masterminds/semver/v3"
"github.com/cavaliergopher/grab/v3"
"github.com/go-semantic-release/semantic-release/v2/pkg/plugin"
"github.com/go-semantic-release/semantic-release/v2/pkg/plugin/discovery/resolver"
"github.com/schollz/progressbar/v3"
)

Expand Down Expand Up @@ -47,13 +44,14 @@ func showDownloadProgressBar(name string, res *grab.Response) {
<-done
}

func downloadPlugin(name, targetPath, downloadUrl, checksum string, showProgress bool) (string, error) {
req, err := grab.NewRequest(targetPath, downloadUrl)
func downloadPlugin(pluginInfo *plugin.PluginInfo, downloadInfo *resolver.PluginDownloadInfo, showProgress bool) (string, error) {
targetPath := path.Join(pluginInfo.PluginPath, downloadInfo.Version, downloadInfo.FileName)
req, err := grab.NewRequest(targetPath, downloadInfo.URL)
if err != nil {
return "", err
}
if checksum != "" {
sum, err := hex.DecodeString(checksum)
if downloadInfo.Checksum != "" {
sum, err := hex.DecodeString(downloadInfo.Checksum)
if err != nil {
return "", err
}
Expand All @@ -62,7 +60,7 @@ func downloadPlugin(name, targetPath, downloadUrl, checksum string, showProgress

res := grab.DefaultClient.Do(req)
if showProgress {
showDownloadProgressBar(name, res)
showDownloadProgressBar(pluginInfo.NormalizedName, res)
}
if err := res.Err(); err != nil {
return "", err
Expand All @@ -72,43 +70,3 @@ func downloadPlugin(name, targetPath, downloadUrl, checksum string, showProgress
}
return res.Filename, nil
}

func fetchPlugin(name, pth string, cons *semver.Constraints, showProgress bool) (string, error) {
pluginInfo, err := getPluginInfo(name)
if err != nil {
return "", err
}

foundVersion := ""
if cons == nil {
foundVersion = pluginInfo.LatestRelease
} else {
versions := make(semver.Collection, 0)
for v := range pluginInfo.Versions {
pv, err := semver.NewVersion(v)
if err != nil {
return "", err
}
versions = append(versions, pv)
}
sort.Sort(sort.Reverse(versions))
for _, v := range versions {
if cons.Check(v) {
foundVersion = v.String()
break
}
}
}

if foundVersion == "" {
return "", errors.New("version not found")
}

releaseAsset := pluginInfo.Versions[foundVersion].getMatchingAsset()
if releaseAsset == nil {
return "", fmt.Errorf("a matching plugin was not found for %s/%s", runtime.GOOS, runtime.GOARCH)
}

targetPath := path.Join(pth, foundVersion, releaseAsset.FileName)
return downloadPlugin(name, targetPath, releaseAsset.URL, releaseAsset.Checksum, showProgress)
}
62 changes: 35 additions & 27 deletions pkg/plugin/discovery/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,71 +8,79 @@ import (
"runtime"
"sort"

"github.com/go-semantic-release/semantic-release/v2/pkg/plugin"

"github.com/Masterminds/semver/v3"
)

const PluginDir = ".semrel"

var osArchDir = runtime.GOOS + "_" + runtime.GOARCH

func getPluginPath(name string) string {
pElem := append([]string{PluginDir}, osArchDir, name)
return path.Join(pElem...)
}

func ensurePluginDir(pth string) error {
_, err := os.Stat(pth)
if os.IsNotExist(err) {
return os.MkdirAll(pth, 0755)
func setAndEnsurePluginPath(pluginInfo *plugin.PluginInfo) error {
pluginPath := path.Join(PluginDir, osArchDir, pluginInfo.NormalizedName)
if _, err := os.Stat(pluginPath); os.IsNotExist(err) {
if err := os.MkdirAll(pluginPath, 0755); err != nil {
return err
}
} else if err != nil {
return err
}
return err
pluginInfo.PluginPath = pluginPath
return nil
}

func getMatchingVersionDir(pth string, cons *semver.Constraints) (string, error) {
vDirs, err := ioutil.ReadDir(pth)
var ErrPluginNotFound = errors.New("no plugin was found")

func getMatchingVersionDir(pluginInfo *plugin.PluginInfo) (string, error) {
vDirs, err := ioutil.ReadDir(pluginInfo.PluginPath)
if err != nil {
return "", err
}
foundVers := make(semver.Collection, 0)
foundVersions := make(semver.Collection, 0)
for _, f := range vDirs {
if f.IsDir() {
fVer, err := semver.NewVersion(f.Name())
if err != nil {
continue
}
foundVers = append(foundVers, fVer)
foundVersions = append(foundVersions, fVer)
}
}

if len(foundVers) == 0 {
return "", errors.New("no installed version found")
if len(foundVersions) == 0 {
return "", nil
}
sort.Sort(sort.Reverse(foundVers))
sort.Sort(sort.Reverse(foundVersions))

if cons == nil {
return path.Join(pth, foundVers[0].String()), nil
if pluginInfo.Constraint == nil {
return path.Join(pluginInfo.PluginPath, foundVersions[0].String()), nil
}

for _, v := range foundVers {
if cons.Check(v) {
return path.Join(pth, v.String()), nil
for _, v := range foundVersions {
if pluginInfo.Constraint.Check(v) {
return path.Join(pluginInfo.PluginPath, v.String()), nil
}
}
return "", errors.New("no matching version found")
return "", nil
}

func findPluginLocally(pth string, cons *semver.Constraints) (string, error) {
vPth, err := getMatchingVersionDir(pth, cons)
func findPluginLocally(pluginInfo *plugin.PluginInfo) (string, error) {
vPth, err := getMatchingVersionDir(pluginInfo)
if err != nil {
return "", err
}

if vPth == "" {
return "", ErrPluginNotFound
}

files, err := ioutil.ReadDir(vPth)
if err != nil {
return "", err
}
if len(files) == 0 {
return "", errors.New("no plugins found")
return "", ErrPluginNotFound
}
for _, f := range files {
if f.IsDir() {
Expand All @@ -83,5 +91,5 @@ func findPluginLocally(pth string, cons *semver.Constraints) (string, error) {
}
return path.Join(vPth, f.Name()), nil
}
return "", errors.New("no matching plugin found")
return "", ErrPluginNotFound
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package discovery
package registry

import (
"encoding/json"
Expand Down Expand Up @@ -34,8 +34,6 @@ func (r *apiPluginRelease) getMatchingAsset() *apiPluginAsset {
}

type apiPlugin struct {
Type string
Name string
LatestRelease string
Versions map[string]*apiPluginRelease
}
Expand Down

0 comments on commit f61374a

Please sign in to comment.