Skip to content

Commit 8eb4824

Browse files
committed
lookup/validate tags using Github API
1 parent c34ad7b commit 8eb4824

9 files changed

+146
-39
lines changed

apis/apis.go

+25-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,33 @@ import (
44
"fmt"
55
"io/ioutil"
66
"net/http"
7+
"os"
8+
"strings"
79
)
810

9-
func get(url string) ([]byte, error) {
10-
resp, err := http.Get(url)
11+
const (
12+
OfflineKey = "M2T_OFFLINE"
13+
GithubCredentialsKey = "M2T_GITHUB"
14+
// GitlabsCredentialsKey = "M2T_GITLAB"
15+
)
16+
17+
func get(url string, credsKey string) ([]byte, error) {
18+
req, err := http.NewRequest("GET", url, nil)
19+
if err != nil {
20+
return nil, err
21+
}
22+
23+
if credsKey != "" {
24+
creds := os.Getenv(credsKey)
25+
if creds != "" {
26+
credsSlice := strings.Split(creds, ":")
27+
if len(credsSlice) == 2 {
28+
req.SetBasicAuth(credsSlice[0], credsSlice[1])
29+
}
30+
}
31+
}
32+
33+
resp, err := http.DefaultClient.Do(req)
1134
if err != nil {
1235
return nil, fmt.Errorf("GET %s: %v", url, err)
1336
}

apis/github.go

+53-9
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,71 @@ package apis
22

33
import (
44
"encoding/json"
5+
"errors"
56
"fmt"
67
"net/url"
8+
"strings"
79
)
810

911
type GithubCommit struct {
10-
ID string `json:"sha"`
12+
SHA string `json:"sha"`
1113
}
1214

13-
func GetGithubCommit(account, project, ref string) (*GithubCommit, error) {
15+
type GithubRef struct {
16+
Ref string `json:"ref"`
17+
}
18+
19+
var githubRateLimitError = fmt.Sprintf(`Github API rate limit exceeded. Please either:
20+
- set %s environment variable to your Github "username:personal_access_token"
21+
to let modules2tuple call Github API using basic authentication.
22+
To create a new token, navigate to https://github.com/settings/tokens/new
23+
(leave all checkboxes unchecked, modules2tuple doesn't need any access to your account)
24+
- set %s=1 or pass "-offline" flag to module2tuple to disable network access`, OfflineKey, GithubCredentialsKey)
25+
26+
func GetGithubCommit(account, project, tag string) (string, error) {
27+
projectID := fmt.Sprintf("%s/%s", url.PathEscape(account), url.PathEscape(project))
28+
url := fmt.Sprintf("https://api.github.com/repos/%s/commits/%s", projectID, tag)
29+
30+
resp, err := get(url, GithubCredentialsKey)
31+
if err != nil {
32+
if strings.Contains(err.Error(), "API rate limit exceeded") {
33+
return "", errors.New(githubRateLimitError)
34+
}
35+
return "", fmt.Errorf("error getting commit %s for %s/%s: %v", tag, account, project, err)
36+
}
37+
38+
var res GithubCommit
39+
if err := json.Unmarshal(resp, &res); err != nil {
40+
return "", fmt.Errorf("error unmarshalling: %v, resp: %v", err, string(resp))
41+
}
42+
43+
return res.SHA, nil
44+
}
45+
46+
func LookupGithubTag(account, project, tag string) (string, error) {
1447
projectID := fmt.Sprintf("%s/%s", url.PathEscape(account), url.PathEscape(project))
15-
url := fmt.Sprintf("https://api.github.com/repos/%s/commits/%s", projectID, ref)
48+
url := fmt.Sprintf("https://api.github.com/repos/%s/git/refs/tags", projectID)
1649

17-
resp, err := get(url)
50+
resp, err := get(url, GithubCredentialsKey)
1851
if err != nil {
19-
return nil, fmt.Errorf("error getting commit %s for %s/%s: %v", ref, account, project, err)
52+
if strings.Contains(err.Error(), "API rate limit exceeded") {
53+
return "", errors.New(githubRateLimitError)
54+
}
55+
return "", fmt.Errorf("error getting refs for %s/%s: %v", account, project, err)
56+
}
57+
58+
var res []GithubRef
59+
if err := json.Unmarshal(resp, &res); err != nil {
60+
return "", fmt.Errorf("error unmarshalling: %v, resp: %v", err, string(resp))
2061
}
2162

22-
var ret GithubCommit
23-
if err := json.Unmarshal(resp, &ret); err != nil {
24-
return nil, fmt.Errorf("error unmarshalling: %v, resp: %v", err, string(resp))
63+
// Github API returns tags sorted by creation time, earliest first.
64+
// Iterate through them in reverse order to find the most recent matching tag.
65+
for i := len(res) - 1; i >= 0; i-- {
66+
if strings.HasSuffix(res[i].Ref, "/"+tag) {
67+
return strings.TrimPrefix(res[i].Ref, "refs/tags/"), nil
68+
}
2569
}
2670

27-
return &ret, nil
71+
return "", fmt.Errorf("tag %v doesn't seem to exist in %s/%s", tag, account, project)
2872
}

apis/github_test.go

+25-4
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,40 @@ import "testing"
66

77
func TestGetGithubCommit(t *testing.T) {
88
examples := []struct {
9-
account, project, ref, ID string
9+
account, project, ref, hash string
1010
}{
1111
{"dmgk", "modules2tuple", "v1.9.0", "fc09878b93db35aafc74311f7ea6684ac08a3b83"},
1212
{"dmgk", "modules2tuple", "a0cdb416ca2c", "a0cdb416ca2cbf6d3dad67a97f4fdcfac954503e"},
1313
}
1414

1515
for i, x := range examples {
16-
c, err := GetGithubCommit(x.account, x.project, x.ref)
16+
hash, err := GetGithubCommit(x.account, x.project, x.ref)
1717
if err != nil {
1818
t.Fatal(err)
1919
}
20-
if x.ID != c.ID {
21-
t.Errorf("expected commit ID %s, got %s (example %d)", x.ID, c.ID, i)
20+
if x.hash != hash {
21+
t.Errorf("expected commit hash %s, got %s (example %d)", x.hash, hash, i)
22+
}
23+
}
24+
}
25+
26+
func TestLookupGithubTag(t *testing.T) {
27+
examples := []struct {
28+
account, project, given, expected string
29+
}{
30+
{"hashicorp", "vault", "v1.0.4", "api/v1.0.4"},
31+
{"hashicorp", "vault", "v1.3.4", "v1.3.4"},
32+
// this repo has earlier mathing tag "codec/codecgen/v1.1.7"
33+
{"ugorji", "go", "v1.1.7", "v1.1.7"},
34+
}
35+
36+
for i, x := range examples {
37+
tag, err := LookupGithubTag(x.account, x.project, x.given)
38+
if err != nil {
39+
t.Fatal(err)
40+
}
41+
if x.expected != tag {
42+
t.Errorf("expected tag %s, got %s (example %d)", x.expected, tag, i)
2243
}
2344
}
2445
}

apis/gitlab.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,22 @@ import (
77
)
88

99
type GitlabCommit struct {
10-
ID string `json:"id"`
10+
SHA string `json:"id"`
1111
}
1212

13-
func GetGitlabCommit(site, account, project, commit string) (*GitlabCommit, error) {
13+
func GetGitlabCommit(site, account, project, commit string) (string, error) {
1414
projectID := url.PathEscape(fmt.Sprintf("%s/%s", account, project))
1515
url := fmt.Sprintf("%s/api/v4/projects/%s/repository/commits/%s", site, projectID, commit)
1616

17-
resp, err := get(url)
17+
resp, err := get(url, "")
1818
if err != nil {
19-
return nil, fmt.Errorf("error getting commit %s for %s/%s: %v", commit, account, project, err)
19+
return "", fmt.Errorf("error getting commit %s for %s/%s: %v", commit, account, project, err)
2020
}
2121

22-
var ret GitlabCommit
23-
if err := json.Unmarshal(resp, &ret); err != nil {
24-
return nil, fmt.Errorf("error unmarshalling: %v, resp: %v", err, string(resp))
22+
var res GitlabCommit
23+
if err := json.Unmarshal(resp, &res); err != nil {
24+
return "", fmt.Errorf("error unmarshalling: %v, resp: %v", err, string(resp))
2525
}
2626

27-
return &ret, nil
27+
return res.SHA, nil
2828
}

apis/gitlab_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ import "testing"
66

77
func TestGetGitlabCommit(t *testing.T) {
88
examples := []struct {
9-
site, account, project, ref, ID string
9+
site, account, project, ref, hash string
1010
}{
1111
{"https://gitlab.com", "gitlab-org", "gitaly-proto", "v1.32.0", "f4db5d05d437abe1154d7308ca044d3577b5ccba"},
1212
{"https://gitlab.com", "gitlab-org", "labkit", "0c3fc7cdd57c", "0c3fc7cdd57c57da5ab474aa72b6640d2bdc9ebb"},
1313
}
1414

1515
for i, x := range examples {
16-
c, err := GetGitlabCommit(x.site, x.account, x.project, x.ref)
16+
hash, err := GetGitlabCommit(x.site, x.account, x.project, x.ref)
1717
if err != nil {
1818
t.Fatal(err)
1919
}
20-
if x.ID != c.ID {
21-
t.Errorf("expected commit ID %s, got %s (example %d)", x.ID, c.ID, i)
20+
if x.hash != hash {
21+
t.Errorf("expected commit hash %s, got %s (example %d)", x.hash, hash, i)
2222
}
2323
}
2424
}

main.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"path"
88
"text/template"
99

10+
"github.com/dmgk/modules2tuple/apis"
1011
"github.com/dmgk/modules2tuple/tuple"
1112
)
1213

@@ -25,14 +26,19 @@ func main() {
2526
os.Exit(1)
2627
}
2728

29+
var haveTuples bool
2830
parser := tuple.NewParser(flagPackagePrefix, flagOffline)
2931
tuples, errors := parser.Load(args[0])
3032
if len(tuples) != 0 {
3133
fmt.Print(tuples)
34+
haveTuples = true
3235
}
3336
if errors != nil {
34-
fmt.Println()
37+
if haveTuples {
38+
fmt.Println()
39+
}
3540
fmt.Print(errors)
41+
fmt.Println()
3642
}
3743
}
3844

@@ -53,7 +59,7 @@ this commit ID translation can be disabled with -offline flag.
5359
`))
5460

5561
var (
56-
flagOffline = false
62+
flagOffline = os.Getenv(apis.OfflineKey) != ""
5763
flagPackagePrefix = "vendor"
5864
flagVersion = false
5965
)

tuple/parser.go

+18-5
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,28 @@ func (p *Parser) Read(r io.Reader) (Tuples, error) {
5050
return
5151
}
5252
if !p.offline {
53-
// Call Gitlab API to translate go.mod short commit IDs and tags
54-
// to the full 40-character commit IDs as required by bsd.sites.mk
55-
if _, ok := t.Source.(GL); ok {
56-
c, err := apis.GetGitlabCommit(t.Source.Site(), t.Account, t.Project, t.Tag)
53+
switch t.Source.(type) {
54+
case GH:
55+
if strings.HasPrefix(t.Tag, "v") {
56+
// Call Gihub API to check tags. Go seem to be able to magically
57+
// translate tags like "v1.0.4" to the "api/v1.0.4" which is really used
58+
// by upstream. We'll try to do the same.
59+
tag, err := apis.LookupGithubTag(t.Account, t.Project, t.Tag)
60+
if err != nil {
61+
ch <- err
62+
return
63+
}
64+
t.Tag = tag
65+
}
66+
case GL:
67+
// Call Gitlab API to translate go.mod short commit IDs and tags
68+
// to the full 40-character commit IDs as required by bsd.sites.mk
69+
hash, err := apis.GetGitlabCommit(t.Source.Site(), t.Account, t.Project, t.Tag)
5770
if err != nil {
5871
ch <- err
5972
return
6073
}
61-
t.Tag = c.ID
74+
t.Tag = hash
6275
}
6376
}
6477
ch <- t

tuple/tuple.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,14 @@ func (tt Tuples) EnsureUniqueGithubProjectAndTag() error {
8282
if t.Account != prevTuple.Account {
8383
// different Account, but the same Project and Tag
8484
if t.Project == prevTuple.Project && t.Tag == prevTuple.Tag {
85-
c, err := apis.GetGithubCommit(t.Account, t.Project, t.Tag)
85+
hash, err := apis.GetGithubCommit(t.Account, t.Project, t.Tag)
8686
if err != nil {
8787
return DuplicateProjectAndTag(t.String())
8888
}
89-
if len(c.ID) < 12 {
90-
return errors.New("unexpectedly short commit ID")
89+
if len(hash) < 12 {
90+
return errors.New("unexpectedly short Githib commit hash")
9191
}
92-
t.Tag = c.ID[:12]
92+
t.Tag = hash[:12]
9393
}
9494
}
9595
}

tuple/tuple_online_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ func TestUniqueProjectAndTag(t *testing.T) {
2222
}
2323
out := tt.String()
2424
if out != expected {
25-
t.Errorf("expected output %s, got %s", expected, out)
25+
t.Errorf("expected output\n%s, got\n%s", expected, out)
2626
}
2727
}

0 commit comments

Comments
 (0)