Skip to content

Commit e7573ff

Browse files
heschigopherbot
authored andcommitted
internal/task: start x repo tagging tasks
First step: list all our projects/repositories, and select those that have a go.mod labeling them as golang.org/x as candidates to release as the next version, or v0.1.0 for untagged repositories. For golang/go#48523. Change-Id: Ice92319a0726daf3bf5f94581582d8802640dffc Reviewed-on: https://go-review.googlesource.com/c/build/+/425088 Reviewed-by: Dmitri Shuralyov <[email protected]> Auto-Submit: Heschi Kreinick <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> Run-TryBot: Heschi Kreinick <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 29d2689 commit e7573ff

File tree

7 files changed

+219
-11
lines changed

7 files changed

+219
-11
lines changed

Diff for: gerrit/gerrit.go

+52-11
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ import (
1212
"bufio"
1313
"bytes"
1414
"context"
15+
"encoding/base64"
1516
"encoding/json"
1617
"errors"
1718
"fmt"
1819
"io"
19-
"io/ioutil"
2020
"net/http"
2121
"net/url"
2222
"sort"
@@ -116,11 +116,17 @@ type urlValues url.Values
116116

117117
func (urlValues) isDoArg() {}
118118

119+
// respBodyRaw returns the body of the response. If set, dst is ignored.
120+
type respBodyRaw struct{ rc *io.ReadCloser }
121+
122+
func (respBodyRaw) isDoArg() {}
123+
119124
func (c *Client) do(ctx context.Context, dst interface{}, method, path string, opts ...doArg) error {
120125
var arg url.Values
121-
var body io.Reader
126+
var requestBody io.Reader
122127
var contentType string
123128
var wantStatus = http.StatusOK
129+
var responseBody *io.ReadCloser
124130
for _, opt := range opts {
125131
switch opt := opt.(type) {
126132
case wantResStatus:
@@ -130,13 +136,15 @@ func (c *Client) do(ctx context.Context, dst interface{}, method, path string, o
130136
if err != nil {
131137
return err
132138
}
133-
body = bytes.NewReader(b)
139+
requestBody = bytes.NewReader(b)
134140
contentType = "application/json"
135141
case reqBodyRaw:
136-
body = opt.r
142+
requestBody = opt.r
137143
contentType = "application/octet-stream"
138144
case urlValues:
139145
arg = url.Values(opt)
146+
case respBodyRaw:
147+
responseBody = opt.rc
140148
default:
141149
panic(fmt.Sprintf("internal error; unsupported type %T", opt))
142150
}
@@ -148,12 +156,11 @@ func (c *Client) do(ctx context.Context, dst interface{}, method, path string, o
148156
if _, ok := c.auth.(noAuth); ok {
149157
slashA = ""
150158
}
151-
var err error
152159
u := c.url + slashA + path
153160
if arg != nil {
154161
u += "?" + arg.Encode()
155162
}
156-
req, err := http.NewRequestWithContext(ctx, method, u, body)
163+
req, err := http.NewRequestWithContext(ctx, method, u, requestBody)
157164
if err != nil {
158165
return err
159166
}
@@ -167,16 +174,27 @@ func (c *Client) do(ctx context.Context, dst interface{}, method, path string, o
167174
if err != nil {
168175
return err
169176
}
170-
defer res.Body.Close()
177+
defer func() {
178+
if responseBody != nil && *responseBody != nil {
179+
// We've handed off the body to the user.
180+
return
181+
}
182+
res.Body.Close()
183+
}()
171184

172185
if res.StatusCode != wantStatus {
173-
body, err := ioutil.ReadAll(io.LimitReader(res.Body, 4<<10))
186+
body, err := io.ReadAll(io.LimitReader(res.Body, 4<<10))
174187
return &HTTPError{res, body, err}
175188
}
176189

190+
if responseBody != nil {
191+
*responseBody = res.Body
192+
return nil
193+
}
194+
177195
if dst == nil {
178196
// Drain the response body, return an error if it's anything but empty.
179-
body, err := ioutil.ReadAll(io.LimitReader(res.Body, 4<<10))
197+
body, err := io.ReadAll(io.LimitReader(res.Body, 4<<10))
180198
if err != nil || len(body) != 0 {
181199
return &HTTPError{res, body, err}
182200
}
@@ -711,7 +729,7 @@ type ProjectInfo struct {
711729

712730
// ListProjects returns the server's active projects.
713731
//
714-
// The returned slice is sorted by project ID and excludes the "All-Projects" project.
732+
// The returned slice is sorted by project ID and excludes the "All-Projects" and "All-Users" projects.
715733
//
716734
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#list-projects
717735
func (c *Client) ListProjects(ctx context.Context) ([]ProjectInfo, error) {
@@ -722,12 +740,15 @@ func (c *Client) ListProjects(ctx context.Context) ([]ProjectInfo, error) {
722740
}
723741
var ret []ProjectInfo
724742
for name, pi := range res {
725-
if name == "All-Projects" {
743+
if name == "All-Projects" || name == "All-Users" {
726744
continue
727745
}
728746
if pi.State != "ACTIVE" {
729747
continue
730748
}
749+
// https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#project-info:
750+
// "name not set if returned in a map where the project name is used as map key"
751+
pi.Name = name
731752
ret = append(ret, pi)
732753
}
733754
sort.Slice(ret, func(i, j int) bool { return ret[i].ID < ret[j].ID })
@@ -828,6 +849,26 @@ func (c *Client) GetBranch(ctx context.Context, project, branch string) (BranchI
828849
return res, err
829850
}
830851

852+
// GetFileContent gets a file's contents at a particular commit.
853+
//
854+
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-content-from-commit.
855+
func (c *Client) GetFileContent(ctx context.Context, project, commit, path string) (io.ReadCloser, error) {
856+
var body io.ReadCloser
857+
err := c.do(ctx, nil, "GET", fmt.Sprintf("/projects/%s/commits/%s/files/%s/content", project, commit, url.QueryEscape(path)), respBodyRaw{&body})
858+
if err != nil {
859+
return nil, err
860+
}
861+
return readCloser{
862+
Reader: base64.NewDecoder(base64.StdEncoding, body),
863+
Closer: body,
864+
}, nil
865+
}
866+
867+
type readCloser struct {
868+
io.Reader
869+
io.Closer
870+
}
871+
831872
// WebLinkInfo is information about a web link.
832873
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#web-link-info
833874
type WebLinkInfo struct {

Diff for: go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ require (
4646
go4.org v0.0.0-20180809161055-417644f6feb5
4747
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70
4848
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
49+
golang.org/x/mod v0.5.1
4950
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
5051
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
5152
golang.org/x/perf v0.0.0-20220913151710-7c6e287988f3

Diff for: go.sum

+3
Original file line numberDiff line numberDiff line change
@@ -946,7 +946,10 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
946946
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
947947
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
948948
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
949+
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
949950
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
951+
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
952+
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
950953
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
951954
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
952955
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

Diff for: internal/relui/buildrelease_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,7 @@ type fakeGerrit struct {
562562
changesCreated int
563563
createdTags map[string]string
564564
wantReviewers []string
565+
task.GerritClient
565566
}
566567

567568
func (g *fakeGerrit) CreateAutoSubmitChange(ctx context.Context, input gerrit.ChangeInput, reviewers []string, contents map[string]string) (string, error) {

Diff for: internal/task/gerrit.go

+26
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"io"
78
"strings"
89

910
"golang.org/x/build/gerrit"
@@ -27,6 +28,10 @@ type GerritClient interface {
2728
ListTags(ctx context.Context, project string) ([]string, error)
2829
// ReadBranchHead returns the head of a branch in project.
2930
ReadBranchHead(ctx context.Context, project, branch string) (string, error)
31+
// ListProjects lists all the projects on the server.
32+
ListProjects(ctx context.Context) ([]string, error)
33+
// ReadFile reads a file from project at the specified commit.
34+
ReadFile(ctx context.Context, project, commit, file string) ([]byte, error)
3035
}
3136

3237
type RealGerritClient struct {
@@ -149,6 +154,27 @@ func (c *RealGerritClient) ReadBranchHead(ctx context.Context, project, branch s
149154
return branchInfo.Revision, nil
150155
}
151156

157+
func (c *RealGerritClient) ListProjects(ctx context.Context) ([]string, error) {
158+
projects, err := c.Client.ListProjects(ctx)
159+
if err != nil {
160+
return nil, err
161+
}
162+
var names []string
163+
for _, p := range projects {
164+
names = append(names, p.Name)
165+
}
166+
return names, nil
167+
}
168+
169+
func (c *RealGerritClient) ReadFile(ctx context.Context, project, commit, file string) ([]byte, error) {
170+
body, err := c.Client.GetFileContent(ctx, project, commit, file)
171+
if err != nil {
172+
return nil, err
173+
}
174+
defer body.Close()
175+
return io.ReadAll(body)
176+
}
177+
152178
// ChangeLink returns a link to the review page for the CL with the specified
153179
// change ID. The change ID must be in the project~cl# form.
154180
func ChangeLink(changeID string) string {

Diff for: internal/task/tagx.go

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package task
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
8+
"golang.org/x/build/gerrit"
9+
wf "golang.org/x/build/internal/workflow"
10+
"golang.org/x/mod/modfile"
11+
"golang.org/x/mod/semver"
12+
)
13+
14+
type TagXReposTasks struct {
15+
Gerrit GerritClient
16+
}
17+
18+
type TagRepo struct {
19+
Name string
20+
Next string
21+
Deps []string
22+
}
23+
24+
func (x *TagXReposTasks) SelectRepos(ctx *wf.TaskContext) ([]*TagRepo, error) {
25+
projects, err := x.Gerrit.ListProjects(ctx)
26+
if err != nil {
27+
return nil, err
28+
}
29+
30+
ctx.Printf("Examining repositories %v", projects)
31+
var repos []*TagRepo
32+
for _, p := range projects {
33+
repo, err := x.readRepo(ctx, p)
34+
if err != nil {
35+
return nil, err
36+
}
37+
if repo != nil {
38+
repos = append(repos, repo)
39+
}
40+
}
41+
return repos, nil
42+
}
43+
44+
func (x *TagXReposTasks) readRepo(ctx *wf.TaskContext, project string) (*TagRepo, error) {
45+
head, err := x.Gerrit.ReadBranchHead(ctx, project, "master")
46+
if errors.Is(err, gerrit.ErrResourceNotExist) {
47+
ctx.Printf("ignoring %v: no master branch: %v", project, err)
48+
return nil, nil
49+
}
50+
if err != nil {
51+
return nil, err
52+
}
53+
54+
gomod, err := x.Gerrit.ReadFile(ctx, project, head, "go.mod")
55+
if errors.Is(err, gerrit.ErrResourceNotExist) {
56+
ctx.Printf("ignoring %v: no go.mod: %v", project, err)
57+
return nil, nil
58+
}
59+
if err != nil {
60+
return nil, err
61+
}
62+
mf, err := modfile.ParseLax("go.mod", gomod, nil)
63+
if err != nil {
64+
return nil, err
65+
}
66+
if !strings.HasPrefix(mf.Module.Mod.Path, "golang.org/x") {
67+
ctx.Printf("ignoring %v: not golang.org/x", project)
68+
return nil, nil
69+
}
70+
71+
tags, err := x.Gerrit.ListTags(ctx, project)
72+
if err != nil {
73+
return nil, err
74+
}
75+
highestRelease := ""
76+
for _, tag := range tags {
77+
if semver.IsValid(tag) && semver.Prerelease(tag) == "" &&
78+
(highestRelease == "" || semver.Compare(highestRelease, tag) < 0) {
79+
highestRelease = tag
80+
}
81+
}
82+
nextTag := "v0.1.0"
83+
if highestRelease != "" {
84+
var err error
85+
nextTag, err = nextVersion(highestRelease)
86+
if err != nil {
87+
return nil, fmt.Errorf("couldn't pick next version for %v: %v", project, err)
88+
}
89+
}
90+
91+
result := &TagRepo{
92+
Name: project,
93+
Next: nextTag,
94+
}
95+
for _, req := range mf.Require {
96+
if strings.HasPrefix(req.Mod.Path, "golang.org/x") {
97+
result.Deps = append(result.Deps, req.Mod.Path)
98+
}
99+
}
100+
return result, nil
101+
}

Diff for: internal/task/tagx_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package task
2+
3+
import (
4+
"context"
5+
"flag"
6+
"testing"
7+
8+
"golang.org/x/build/gerrit"
9+
"golang.org/x/build/internal/workflow"
10+
)
11+
12+
var flagRunTagXTest = flag.Bool("run-tagx-test", false, "run tag x/ repo test, which is read-only and safe. Must have a Gerrit cookie in gitcookies.")
13+
14+
func TestSelectReposLive(t *testing.T) {
15+
if !*flagRunTagXTest {
16+
t.Skip("Not enabled by flags")
17+
}
18+
19+
tasks := &TagXReposTasks{
20+
Gerrit: &RealGerritClient{
21+
Client: gerrit.NewClient("https://go-review.googlesource.com", gerrit.GitCookiesAuth()),
22+
},
23+
}
24+
ctx := &workflow.TaskContext{
25+
Context: context.Background(),
26+
Logger: &testLogger{t},
27+
}
28+
repos, err := tasks.SelectRepos(ctx)
29+
if err != nil {
30+
t.Fatal(err)
31+
}
32+
for _, r := range repos {
33+
t.Logf("%#v", r)
34+
}
35+
}

0 commit comments

Comments
 (0)