Skip to content

Commit 1b57e43

Browse files
feat: add github resolver
1 parent c58af0b commit 1b57e43

File tree

6 files changed

+201
-5
lines changed

6 files changed

+201
-5
lines changed

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ go 1.18
55
require (
66
github.com/Masterminds/semver/v3 v3.1.1
77
github.com/cavaliergopher/grab/v3 v3.0.1
8+
github.com/google/go-github/v45 v45.0.0
89
github.com/hashicorp/go-hclog v1.2.0
910
github.com/hashicorp/go-plugin v1.4.4
1011
github.com/schollz/progressbar/v3 v3.8.6
1112
github.com/spf13/cobra v1.4.0
1213
github.com/spf13/viper v1.11.0
1314
github.com/stretchr/testify v1.7.1
15+
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
1416
google.golang.org/grpc v1.46.2
1517
google.golang.org/protobuf v1.28.0
1618
)
@@ -20,6 +22,7 @@ require (
2022
github.com/fatih/color v1.13.0 // indirect
2123
github.com/fsnotify/fsnotify v1.5.4 // indirect
2224
github.com/golang/protobuf v1.5.2 // indirect
25+
github.com/google/go-querystring v1.1.0 // indirect
2326
github.com/hashicorp/hcl v1.0.0 // indirect
2427
github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect
2528
github.com/inconshreveable/mousetrap v1.0.0 // indirect
@@ -45,6 +48,7 @@ require (
4548
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
4649
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect
4750
golang.org/x/text v0.3.7 // indirect
51+
google.golang.org/appengine v1.6.7 // indirect
4852
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect
4953
gopkg.in/ini.v1 v1.66.4 // indirect
5054
gopkg.in/yaml.v2 v2.4.0 // indirect

go.sum

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,11 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
118118
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
119119
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
120120
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
121-
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
121+
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
122+
github.com/google/go-github/v45 v45.0.0 h1:LU0WBjYidxIVyx7PZeWb+FP4JZJ3Wh3FQgdumnGqiLs=
123+
github.com/google/go-github/v45 v45.0.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28=
124+
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
125+
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
122126
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
123127
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
124128
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -310,6 +314,7 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v
310314
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
311315
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
312316
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
317+
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
313318
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y=
314319
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
315320
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -321,6 +326,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
321326
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
322327
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
323328
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
329+
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE=
330+
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
324331
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
325332
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
326333
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -373,6 +380,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
373380
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
374381
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
375382
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
383+
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
376384
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
377385
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
378386
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
@@ -445,7 +453,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
445453
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
446454
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
447455
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
448-
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
449456
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
450457
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
451458
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -471,6 +478,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
471478
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
472479
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
473480
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
481+
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
474482
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
475483
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
476484
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=

pkg/plugin/discovery/discovery.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/go-semantic-release/semantic-release/v2/pkg/config"
88
"github.com/go-semantic-release/semantic-release/v2/pkg/plugin"
99
"github.com/go-semantic-release/semantic-release/v2/pkg/plugin/discovery/resolver"
10+
"github.com/go-semantic-release/semantic-release/v2/pkg/plugin/discovery/resolver/github"
1011
"github.com/go-semantic-release/semantic-release/v2/pkg/plugin/discovery/resolver/registry"
1112
)
1213

@@ -19,6 +20,9 @@ func loadResolvers(resolvers ...resolver.Resolver) (map[string]resolver.Resolver
1920
resolversMap := make(map[string]resolver.Resolver)
2021
for _, r := range resolvers {
2122
for _, name := range r.Names() {
23+
if name == "default" {
24+
return nil, fmt.Errorf("resolver name default is reserved")
25+
}
2226
if _, ok := resolversMap[name]; ok {
2327
return nil, fmt.Errorf("resolver %s already exists", name)
2428
}
@@ -29,10 +33,13 @@ func loadResolvers(resolvers ...resolver.Resolver) (map[string]resolver.Resolver
2933
}
3034

3135
func New(config *config.Config) (*Discovery, error) {
32-
resolvers, err := loadResolvers(registry.NewResolver())
36+
resolvers, err := loadResolvers(registry.NewResolver(), github.NewResolver())
3337
if err != nil {
3438
return nil, err
3539
}
40+
// use the registry resolver as default
41+
resolvers["default"] = resolvers["registry"]
42+
3643
return &Discovery{
3744
config: config,
3845
resolvers: resolvers,
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io/ioutil"
7+
"net/http"
8+
"os"
9+
"regexp"
10+
"runtime"
11+
"sort"
12+
"strings"
13+
14+
"github.com/Masterminds/semver/v3"
15+
"github.com/go-semantic-release/semantic-release/v2/pkg/plugin"
16+
"github.com/go-semantic-release/semantic-release/v2/pkg/plugin/discovery/resolver"
17+
"github.com/google/go-github/v45/github"
18+
"golang.org/x/oauth2"
19+
)
20+
21+
type GitHubResolver struct {
22+
ghClient *github.Client
23+
}
24+
25+
func NewResolver() *GitHubResolver {
26+
var tc *http.Client
27+
if ghToken := os.Getenv("GITHUB_TOKEN"); ghToken != "" {
28+
tc = oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(&oauth2.Token{AccessToken: ghToken}))
29+
}
30+
return &GitHubResolver{
31+
ghClient: github.NewClient(tc),
32+
}
33+
}
34+
35+
func (g *GitHubResolver) githubReleaseToDownloadInfo(repoOwner, repoName string, release *github.RepositoryRelease) (*resolver.PluginDownloadInfo, error) {
36+
var checksumAsset *github.ReleaseAsset
37+
var pluginAsset *github.ReleaseAsset
38+
osArchRe := regexp.MustCompile("(?i)" + runtime.GOOS + "(_|-)" + runtime.GOARCH)
39+
for _, asset := range release.Assets {
40+
assetName := asset.GetName()
41+
if checksumAsset == nil && asset.GetSize() <= 4096 && strings.Contains(strings.ToLower(assetName), "checksum") {
42+
checksumAsset = asset
43+
}
44+
if pluginAsset == nil && osArchRe.MatchString(assetName) {
45+
pluginAsset = asset
46+
}
47+
}
48+
49+
if pluginAsset == nil {
50+
return nil, fmt.Errorf("no matching plugin binary was found for %s/%s", runtime.GOOS, runtime.GOARCH)
51+
}
52+
53+
foundChecksum := ""
54+
if checksumAsset != nil {
55+
checksumDownload, _, err := g.ghClient.Repositories.DownloadReleaseAsset(context.Background(), repoOwner, repoName, checksumAsset.GetID(), http.DefaultClient)
56+
if err != nil {
57+
return nil, err
58+
}
59+
checksumData, err := ioutil.ReadAll(checksumDownload)
60+
checksumDownload.Close()
61+
if err != nil {
62+
return nil, err
63+
}
64+
for _, l := range strings.Split(string(checksumData), "\n") {
65+
sl := strings.Split(l, " ")
66+
if len(sl) < 3 {
67+
continue
68+
}
69+
if sl[2] == pluginAsset.GetName() {
70+
foundChecksum = sl[0]
71+
}
72+
}
73+
}
74+
75+
return &resolver.PluginDownloadInfo{
76+
URL: pluginAsset.GetBrowserDownloadURL(),
77+
Checksum: foundChecksum,
78+
FileName: pluginAsset.GetName(),
79+
Version: strings.TrimLeft(release.GetTagName(), "v"),
80+
}, nil
81+
}
82+
83+
type ghRelease struct {
84+
version *semver.Version
85+
release *github.RepositoryRelease
86+
}
87+
88+
type ghReleases []*ghRelease
89+
90+
func (gr ghReleases) Len() int { return len(gr) }
91+
func (gr ghReleases) Less(i, j int) bool { return gr[j].version.LessThan(gr[i].version) }
92+
func (gr ghReleases) Swap(i, j int) { gr[i], gr[j] = gr[j], gr[i] }
93+
94+
func (g *GitHubResolver) getAllValidGitHubReleases(repoOwner, repoName string) (ghReleases, error) {
95+
ret := make(ghReleases, 0)
96+
opts := &github.ListOptions{Page: 1, PerPage: 100}
97+
for {
98+
releases, resp, err := g.ghClient.Repositories.ListReleases(context.Background(), repoOwner, repoName, opts)
99+
if err != nil {
100+
return nil, err
101+
}
102+
for _, release := range releases {
103+
if release.GetDraft() {
104+
continue
105+
}
106+
107+
if semverTag, err := semver.NewVersion(release.GetTagName()); err == nil {
108+
ret = append(ret, &ghRelease{version: semverTag, release: release})
109+
}
110+
}
111+
112+
if resp.NextPage == 0 {
113+
break
114+
}
115+
opts.Page = resp.NextPage
116+
}
117+
sort.Sort(ret)
118+
return ret, nil
119+
}
120+
121+
func (g *GitHubResolver) ResolvePlugin(pluginInfo *plugin.PluginInfo) (*resolver.PluginDownloadInfo, error) {
122+
if pluginInfo.RepoSlug == "" {
123+
pluginInfo.RepoSlug = knownPlugins[pluginInfo.ShortNormalizedName]
124+
}
125+
if pluginInfo.RepoSlug == "" {
126+
return nil, fmt.Errorf("repo slug not found")
127+
}
128+
repoOwner, repoName, _ := strings.Cut(pluginInfo.RepoSlug, "/")
129+
130+
var foundRelease *github.RepositoryRelease
131+
if pluginInfo.Constraint == nil {
132+
release, _, err := g.ghClient.Repositories.GetLatestRelease(context.Background(), repoOwner, repoName)
133+
if err != nil {
134+
return nil, err
135+
}
136+
foundRelease = release
137+
} else {
138+
validReleases, err := g.getAllValidGitHubReleases(repoOwner, repoName)
139+
if err != nil {
140+
return nil, err
141+
}
142+
for _, release := range validReleases {
143+
if pluginInfo.Constraint.Check(release.version) {
144+
foundRelease = release.release
145+
break
146+
}
147+
}
148+
}
149+
150+
if foundRelease == nil {
151+
return nil, fmt.Errorf("no matching release was found")
152+
}
153+
154+
di, err := g.githubReleaseToDownloadInfo(repoOwner, repoName, foundRelease)
155+
return di, err
156+
}
157+
158+
func (g *GitHubResolver) Names() []string {
159+
return []string{"github", "gh"}
160+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package github
2+
3+
var knownPlugins = map[string]string{
4+
"provider-github": "go-semantic-release/provider-github",
5+
"provider-gitlab": "go-semantic-release/provider-gitlab",
6+
"changelog-generator-default": "go-semantic-release/changelog-generator-default",
7+
"commit-analyzer-default": "go-semantic-release/commit-analyzer-cz",
8+
"condition-default": "go-semantic-release/condition-default",
9+
"condition-github": "go-semantic-release/condition-github",
10+
"condition-gitlab": "go-semantic-release/condition-gitlab",
11+
"files-updater-npm": "go-semantic-release/files-updater-npm",
12+
"provider-git": "go-semantic-release/provider-git",
13+
"condition-bitbucket": "go-semantic-release/condition-bitbucket",
14+
"files-updater-helm": "go-semantic-release/files-updater-helm",
15+
"hooks-goreleaser": "go-semantic-release/hooks-goreleaser",
16+
"hooks-npm-binary-releaser": "go-semantic-release/hooks-npm-binary-releaser",
17+
}

pkg/plugin/discovery/resolver/registry/registry.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func NewResolver() *RegistryResolver {
1818
return &RegistryResolver{}
1919
}
2020

21-
func (r RegistryResolver) ResolvePlugin(pluginInfo *plugin.PluginInfo) (*resolver.PluginDownloadInfo, error) {
21+
func (r *RegistryResolver) ResolvePlugin(pluginInfo *plugin.PluginInfo) (*resolver.PluginDownloadInfo, error) {
2222
pluginApiRes, err := getPluginInfo(pluginInfo.NormalizedName)
2323
if err != nil {
2424
return nil, err
@@ -62,5 +62,5 @@ func (r RegistryResolver) ResolvePlugin(pluginInfo *plugin.PluginInfo) (*resolve
6262
}
6363

6464
func (r *RegistryResolver) Names() []string {
65-
return []string{"default", "registry"}
65+
return []string{"registry"}
6666
}

0 commit comments

Comments
 (0)