From 581b7eda8968d3f4c0b88c2a3cdc01e9ecde5bb7 Mon Sep 17 00:00:00 2001 From: Christoph Witzko Date: Sun, 29 May 2022 19:27:34 +0200 Subject: [PATCH] refactor: split plugin discovery in differen files --- pkg/hooks/hooks.go | 6 +- pkg/plugin/discovery/discovery.go | 236 +----------------------------- pkg/plugin/discovery/download.go | 114 +++++++++++++++ pkg/plugin/discovery/local.go | 87 +++++++++++ pkg/plugin/discovery/registry.go | 60 ++++++++ pkg/plugin/manager/manager.go | 5 +- 6 files changed, 269 insertions(+), 239 deletions(-) create mode 100644 pkg/plugin/discovery/download.go create mode 100644 pkg/plugin/discovery/local.go create mode 100644 pkg/plugin/discovery/registry.go diff --git a/pkg/hooks/hooks.go b/pkg/hooks/hooks.go index 99e7be36..d1be4c7e 100644 --- a/pkg/hooks/hooks.go +++ b/pkg/hooks/hooks.go @@ -16,9 +16,10 @@ type ChainedHooksExecutor struct { func (c *ChainedHooksExecutor) Success(config *SuccessHookConfig) error { for _, h := range c.HooksChain { + name := h.Name() err := h.Success(config) if err != nil { - return err + return fmt.Errorf("%s hook has failed: %w", name, err) } } return nil @@ -26,9 +27,10 @@ func (c *ChainedHooksExecutor) Success(config *SuccessHookConfig) error { func (c *ChainedHooksExecutor) NoRelease(config *NoReleaseConfig) error { for _, h := range c.HooksChain { + name := h.Name() err := h.NoRelease(config) if err != nil { - return err + return fmt.Errorf("%s hook has failed: %w", name, err) } } return nil diff --git a/pkg/plugin/discovery/discovery.go b/pkg/plugin/discovery/discovery.go index 0730d93f..f9c3a751 100644 --- a/pkg/plugin/discovery/discovery.go +++ b/pkg/plugin/discovery/discovery.go @@ -1,23 +1,11 @@ package discovery import ( - "crypto/sha256" - "encoding/hex" - "encoding/json" "errors" - "fmt" - "io/ioutil" - "net/http" - "os" "os/exec" - "path" - "runtime" - "sort" "strings" - "time" "github.com/Masterminds/semver/v3" - "github.com/cavaliergopher/grab/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" @@ -26,56 +14,8 @@ import ( "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/schollz/progressbar/v3" ) -const PluginDir = ".semrel" -const PluginAPI = "https://plugins.go-semantic-release.xyz/api/v1" - -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) - } - return err -} - -type apiPluginAsset struct { - FileName string - URL string - OS string - Arch string - Checksum string -} - -type apiPluginRelease struct { - CreatedAt time.Time - Assets []*apiPluginAsset -} - -func (r *apiPluginRelease) getMatchingAsset() *apiPluginAsset { - for _, a := range r.Assets { - if a.OS == runtime.GOOS && a.Arch == runtime.GOARCH { - return a - } - } - return nil -} - -type apiPlugin struct { - Type string - Name string - LatestRelease string - Versions map[string]*apiPluginRelease -} - type Discovery struct { config *config.Config } @@ -84,178 +24,6 @@ func New(config *config.Config) (*Discovery, error) { return &Discovery{config}, nil } -func (d *Discovery) getPluginInfo(name string) (*apiPlugin, error) { - res, err := http.Get(fmt.Sprintf("%s/plugins/%s.json", PluginAPI, name)) - if err != nil { - return nil, err - } - defer res.Body.Close() - if res.StatusCode == 404 { - return nil, fmt.Errorf("plugin not found: %s", name) - } - if res.StatusCode < 200 || res.StatusCode >= 300 { - return nil, errors.New("invalid response") - } - var plugin *apiPlugin - if err := json.NewDecoder(res.Body).Decode(&plugin); err != nil { - return nil, err - } - return plugin, nil -} - -func showDownloadProgressBar(name string, res *grab.Response) { - bar := progressbar.NewOptions64( - res.Size(), - progressbar.OptionSetDescription(name), - progressbar.OptionSetWriter(os.Stderr), - progressbar.OptionShowBytes(true), - progressbar.OptionSetWidth(10), - progressbar.OptionThrottle(65*time.Millisecond), - progressbar.OptionShowCount(), - progressbar.OptionSetWidth(40), - progressbar.OptionClearOnFinish(), - progressbar.OptionSetPredictTime(false), - ) - t := time.NewTicker(100 * time.Millisecond) - done := make(chan struct{}) - go func() { - for { - select { - case <-t.C: - _ = bar.Set64(res.BytesComplete()) - case <-res.Done: - _ = bar.Finish() - t.Stop() - done <- struct{}{} - return - } - } - }() - <-done -} - -func (d *Discovery) fetchPlugin(name, pth string, cons *semver.Constraints) (string, error) { - pluginInfo, err := d.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) - - req, err := grab.NewRequest(targetPath, releaseAsset.URL) - if err != nil { - return "", err - } - if releaseAsset.Checksum != "" { - sum, err := hex.DecodeString(releaseAsset.Checksum) - if err != nil { - return "", err - } - req.SetChecksum(sha256.New(), sum, true) - } - - res := grab.DefaultClient.Do(req) - if d.config.ShowProgress { - showDownloadProgressBar(name, res) - } - if err := res.Err(); err != nil { - return "", err - } - if err := os.Chmod(res.Filename, 0755); err != nil { - return "", err - } - - return res.Filename, nil -} - -func getMatchingVersionDir(pth string, cons *semver.Constraints) (string, error) { - vDirs, err := ioutil.ReadDir(pth) - if err != nil { - return "", err - } - foundVers := 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) - } - } - - if len(foundVers) == 0 { - return "", errors.New("no installed version found") - } - sort.Sort(sort.Reverse(foundVers)) - - if cons == nil { - return path.Join(pth, foundVers[0].String()), nil - } - - for _, v := range foundVers { - if cons.Check(v) { - return path.Join(pth, v.String()), nil - } - } - return "", errors.New("no matching version found") -} - -func (d *Discovery) findPluginLocally(pth string, cons *semver.Constraints) (string, error) { - vPth, err := getMatchingVersionDir(pth, cons) - if err != nil { - return "", err - } - - files, err := ioutil.ReadDir(vPth) - if err != nil { - return "", err - } - if len(files) == 0 { - return "", errors.New("no plugins found") - } - for _, f := range files { - if f.IsDir() { - continue - } - if f.Mode()&0100 == 0 { - continue - } - return path.Join(vPth, f.Name()), nil - } - return "", errors.New("no matching plugin found") -} - func getPluginType(t string) string { switch t { case analyzer.CommitAnalyzerPluginName: @@ -296,9 +64,9 @@ func (d *Discovery) FindPlugin(t, name string) (*plugin.PluginOpts, error) { return nil, err } - binPath, err := d.findPluginLocally(pPath, cons) + binPath, err := findPluginLocally(pPath, cons) if err != nil { - binPath, err = d.fetchPlugin(pName, pPath, cons) + binPath, err = fetchPlugin(pName, pPath, cons, d.config.ShowProgress) if err != nil { return nil, err } diff --git a/pkg/plugin/discovery/download.go b/pkg/plugin/discovery/download.go new file mode 100644 index 00000000..7fba6d37 --- /dev/null +++ b/pkg/plugin/discovery/download.go @@ -0,0 +1,114 @@ +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/schollz/progressbar/v3" +) + +func showDownloadProgressBar(name string, res *grab.Response) { + bar := progressbar.NewOptions64( + res.Size(), + progressbar.OptionSetDescription(name), + progressbar.OptionSetWriter(os.Stderr), + progressbar.OptionShowBytes(true), + progressbar.OptionSetWidth(10), + progressbar.OptionThrottle(65*time.Millisecond), + progressbar.OptionShowCount(), + progressbar.OptionSetWidth(40), + progressbar.OptionClearOnFinish(), + progressbar.OptionSetPredictTime(false), + ) + t := time.NewTicker(100 * time.Millisecond) + done := make(chan struct{}) + go func() { + for { + select { + case <-t.C: + _ = bar.Set64(res.BytesComplete()) + case <-res.Done: + _ = bar.Finish() + t.Stop() + done <- struct{}{} + return + } + } + }() + <-done +} + +func downloadPlugin(name, targetPath, downloadUrl, checksum string, showProgress bool) (string, error) { + req, err := grab.NewRequest(targetPath, downloadUrl) + if err != nil { + return "", err + } + if checksum != "" { + sum, err := hex.DecodeString(checksum) + if err != nil { + return "", err + } + req.SetChecksum(sha256.New(), sum, true) + } + + res := grab.DefaultClient.Do(req) + if showProgress { + showDownloadProgressBar(name, res) + } + if err := res.Err(); err != nil { + return "", err + } + if err := os.Chmod(res.Filename, 0755); err != nil { + return "", err + } + 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) +} diff --git a/pkg/plugin/discovery/local.go b/pkg/plugin/discovery/local.go new file mode 100644 index 00000000..e9fef00d --- /dev/null +++ b/pkg/plugin/discovery/local.go @@ -0,0 +1,87 @@ +package discovery + +import ( + "errors" + "io/ioutil" + "os" + "path" + "runtime" + "sort" + + "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) + } + return err +} + +func getMatchingVersionDir(pth string, cons *semver.Constraints) (string, error) { + vDirs, err := ioutil.ReadDir(pth) + if err != nil { + return "", err + } + foundVers := 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) + } + } + + if len(foundVers) == 0 { + return "", errors.New("no installed version found") + } + sort.Sort(sort.Reverse(foundVers)) + + if cons == nil { + return path.Join(pth, foundVers[0].String()), nil + } + + for _, v := range foundVers { + if cons.Check(v) { + return path.Join(pth, v.String()), nil + } + } + return "", errors.New("no matching version found") +} + +func findPluginLocally(pth string, cons *semver.Constraints) (string, error) { + vPth, err := getMatchingVersionDir(pth, cons) + if err != nil { + return "", err + } + + files, err := ioutil.ReadDir(vPth) + if err != nil { + return "", err + } + if len(files) == 0 { + return "", errors.New("no plugins found") + } + for _, f := range files { + if f.IsDir() { + continue + } + if f.Mode()&0100 == 0 { + continue + } + return path.Join(vPth, f.Name()), nil + } + return "", errors.New("no matching plugin found") +} diff --git a/pkg/plugin/discovery/registry.go b/pkg/plugin/discovery/registry.go new file mode 100644 index 00000000..693fbc8a --- /dev/null +++ b/pkg/plugin/discovery/registry.go @@ -0,0 +1,60 @@ +package discovery + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "runtime" + "time" +) + +const PluginAPI = "https://plugins.go-semantic-release.xyz/api/v1" + +type apiPluginAsset struct { + FileName string + URL string + OS string + Arch string + Checksum string +} + +type apiPluginRelease struct { + CreatedAt time.Time + Assets []*apiPluginAsset +} + +func (r *apiPluginRelease) getMatchingAsset() *apiPluginAsset { + for _, a := range r.Assets { + if a.OS == runtime.GOOS && a.Arch == runtime.GOARCH { + return a + } + } + return nil +} + +type apiPlugin struct { + Type string + Name string + LatestRelease string + Versions map[string]*apiPluginRelease +} + +func getPluginInfo(name string) (*apiPlugin, error) { + res, err := http.Get(fmt.Sprintf("%s/plugins/%s.json", PluginAPI, name)) + if err != nil { + return nil, err + } + defer res.Body.Close() + if res.StatusCode == 404 { + return nil, fmt.Errorf("plugin not found: %s", name) + } + if res.StatusCode < 200 || res.StatusCode >= 300 { + return nil, errors.New("invalid response") + } + var plugin *apiPlugin + if err := json.NewDecoder(res.Body).Decode(&plugin); err != nil { + return nil, err + } + return plugin, nil +} diff --git a/pkg/plugin/manager/manager.go b/pkg/plugin/manager/manager.go index abfb5cf5..384db31b 100644 --- a/pkg/plugin/manager/manager.go +++ b/pkg/plugin/manager/manager.go @@ -95,10 +95,9 @@ func (m *PluginManager) GetChainedUpdater() (*updater.ChainedUpdater, error) { updaters = append(updaters, upd.(updater.FilesUpdater)) } - updater := &updater.ChainedUpdater{ + return &updater.ChainedUpdater{ Updaters: updaters, - } - return updater, nil + }, nil } func (m *PluginManager) GetChainedHooksExecutor() (*hooks.ChainedHooksExecutor, error) {