зеркало из https://github.com/golang/build.git
gerrit, internal/task: add Gerrit-related release tasks
I refactored out the Gerrit-related code from MailDLCL, and added support for waiting for submit and creating tags. No test for creating tags but that logic has virtually nothing to it so I think I'm okay with it? For golang/go#51797. Change-Id: Ia8c24536bbee27e0b7bef04769ac5a81dc3021ab Reviewed-on: https://go-review.googlesource.com/c/build/+/408674 Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Alex Rakoczy <alex@golang.org> Run-TryBot: Heschi Kreinick <heschi@google.com> Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
This commit is contained in:
Родитель
1d197f9e3c
Коммит
757f53580f
|
@ -66,7 +66,7 @@ type HTTPError struct {
|
|||
}
|
||||
|
||||
func (e *HTTPError) Error() string {
|
||||
return fmt.Sprintf("HTTP status %s; %s", e.Res.Status, e.Body)
|
||||
return fmt.Sprintf("HTTP status %s on request to %s; %s", e.Res.Status, e.Res.Request.URL, e.Body)
|
||||
}
|
||||
|
||||
// doArg is an optional argument for the Client.do method.
|
||||
|
@ -760,6 +760,13 @@ func (c *Client) ChangeFileContentInChangeEdit(ctx context.Context, changeID str
|
|||
return err
|
||||
}
|
||||
|
||||
// DeleteFileInChangeEdit deletes a file from a change edit.
|
||||
//
|
||||
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-edit-file.
|
||||
func (c *Client) DeleteFileInChangeEdit(ctx context.Context, changeID string, path string) error {
|
||||
return c.do(ctx, nil, "DELETE", "/changes/"+changeID+"/edit/"+url.QueryEscape(path), wantResStatus(http.StatusNoContent))
|
||||
}
|
||||
|
||||
// PublishChangeEdit promotes the change edit to a regular patch set.
|
||||
//
|
||||
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#publish-edit.
|
||||
|
@ -876,6 +883,22 @@ func (c *Client) GetProjectTags(ctx context.Context, name string) (map[string]Ta
|
|||
return m, nil
|
||||
}
|
||||
|
||||
// TagInput contains information for creating a tag.
|
||||
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#tag-input
|
||||
type TagInput struct {
|
||||
// Ref is optional, and when present must be equal to the URL parameter. Removed.
|
||||
Revision string `json:"revision,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// CreateTag creates a tag on project.
|
||||
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#create-tag.
|
||||
func (c *Client) CreateTag(ctx context.Context, project, tag string, input TagInput) (TagInfo, error) {
|
||||
var res TagInfo
|
||||
err := c.do(ctx, &res, "PUT", fmt.Sprintf("/projects/%s/tags/%s", project, url.PathEscape(tag)), reqBodyJSON{&input}, wantResStatus(http.StatusCreated))
|
||||
return res, err
|
||||
}
|
||||
|
||||
// GetAccountInfo gets the specified account's information from Gerrit.
|
||||
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#get-account
|
||||
// The accountID is https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#account-id
|
||||
|
|
|
@ -74,26 +74,16 @@ func MailDLCL(ctx *workflow.TaskContext, versions []string, e ExternalConfig) (c
|
|||
return "(dry-run)", nil
|
||||
}
|
||||
cl := gerrit.NewClient(e.GerritAPI.URL, e.GerritAPI.Auth)
|
||||
c, err := cl.CreateChange(ctx, gerrit.ChangeInput{
|
||||
changeInput := gerrit.ChangeInput{
|
||||
Project: "dl",
|
||||
Subject: "dl: add " + strings.Join(versions, " and "),
|
||||
Branch: "master",
|
||||
})
|
||||
}
|
||||
changeID, err := (&realGerritClient{client: cl}).CreateAutoSubmitChange(ctx, changeInput, files)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
changeID := fmt.Sprintf("%s~%d", c.Project, c.ChangeNumber)
|
||||
for path, content := range files {
|
||||
err := cl.ChangeFileContentInChangeEdit(ctx, changeID, path, content)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
err = cl.PublishChangeEdit(ctx, changeID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("https://go.dev/cl/%d", c.ChangeNumber), nil
|
||||
return changeLink(changeID), nil
|
||||
}
|
||||
|
||||
func verifyGoVersions(versions ...string) error {
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/build/gerrit"
|
||||
)
|
||||
|
||||
type GerritClient interface {
|
||||
// CreateAutoSubmitChange creates a change with the given metadata and contents, sets
|
||||
// Run-TryBots and Auto-Submit, and returns its change ID.
|
||||
// If the content of a file is empty, that file will be deleted from the repository.
|
||||
CreateAutoSubmitChange(ctx context.Context, input gerrit.ChangeInput, contents map[string]string) (string, error)
|
||||
// AwaitSubmit waits for the specified change to be auto-submitted or fail
|
||||
// trybots. If the CL is submitted, returns the submitted commit hash.
|
||||
AwaitSubmit(ctx context.Context, changeID string) (string, error)
|
||||
// Tag creates a tag on project at the specified commit.
|
||||
Tag(ctx context.Context, project, tag, commit string) error
|
||||
}
|
||||
|
||||
type realGerritClient struct {
|
||||
client *gerrit.Client
|
||||
}
|
||||
|
||||
func (c *realGerritClient) CreateAutoSubmitChange(ctx context.Context, input gerrit.ChangeInput, files map[string]string) (string, error) {
|
||||
change, err := c.client.CreateChange(ctx, input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
changeID := fmt.Sprintf("%s~%d", change.Project, change.ChangeNumber)
|
||||
for path, content := range files {
|
||||
if content == "" {
|
||||
if err := c.client.DeleteFileInChangeEdit(ctx, changeID, path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
if err := c.client.ChangeFileContentInChangeEdit(ctx, changeID, path, content); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.client.PublishChangeEdit(ctx, changeID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := c.client.SetReview(ctx, changeID, "current", gerrit.ReviewInput{
|
||||
Labels: map[string]int{
|
||||
"Run-TryBot": 1,
|
||||
"Auto-Submit": 1,
|
||||
},
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return changeID, nil
|
||||
}
|
||||
|
||||
func (c *realGerritClient) AwaitSubmit(ctx context.Context, changeID string) (string, error) {
|
||||
for {
|
||||
detail, err := c.client.GetChangeDetail(ctx, changeID, gerrit.QueryChangesOpt{
|
||||
Fields: []string{"CURRENT_REVISION", "DETAILED_LABELS"},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if detail.Status == "MERGED" {
|
||||
return detail.CurrentRevision, nil
|
||||
}
|
||||
for _, approver := range detail.Labels["TryBot-Result"].All {
|
||||
if approver.Value < 0 {
|
||||
return "", fmt.Errorf("trybots failed on %v", changeLink(changeID))
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return "", ctx.Err()
|
||||
case <-time.After(10 * time.Second):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *realGerritClient) Tag(ctx context.Context, project, tag, commit string) error {
|
||||
_, err := c.client.CreateTag(ctx, project, tag, gerrit.TagInput{
|
||||
Revision: commit,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// changeLink returns a link to the review page for the CL with the specified
|
||||
// change ID. The change ID must be in the project~cl# form.
|
||||
func changeLink(changeID string) string {
|
||||
parts := strings.SplitN(changeID, "~", 3)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Sprintf("(unparseable change ID %q)", changeID)
|
||||
}
|
||||
return "https://go.dev/cl/" + parts[1]
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package task
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/build/gerrit"
|
||||
"golang.org/x/build/internal/workflow"
|
||||
)
|
||||
|
||||
// VersionTasks contains tasks related to versioning the release.
|
||||
type VersionTasks struct {
|
||||
Gerrit GerritClient
|
||||
Project string
|
||||
}
|
||||
|
||||
// CreateAutoSubmitVersionCL mails an auto-submit change to update VERSION on branch.
|
||||
func (t *VersionTasks) CreateAutoSubmitVersionCL(ctx *workflow.TaskContext, branch, version string) (string, error) {
|
||||
return t.Gerrit.CreateAutoSubmitChange(ctx, gerrit.ChangeInput{
|
||||
Project: t.Project,
|
||||
Branch: branch,
|
||||
Subject: fmt.Sprintf("[%v] %v", branch, version),
|
||||
}, map[string]string{
|
||||
"VERSION": version,
|
||||
})
|
||||
}
|
||||
|
||||
// AwaitCL waits for the specified CL to be submitted.
|
||||
func (t *VersionTasks) AwaitCL(ctx *workflow.TaskContext, changeID string) (string, error) {
|
||||
ctx.Printf("Awaiting review/submit of %v", changeLink(changeID))
|
||||
return t.Gerrit.AwaitSubmit(ctx, changeID)
|
||||
}
|
||||
|
||||
// TagRelease tags commit as version.
|
||||
func (t *VersionTasks) TagRelease(ctx *workflow.TaskContext, version, commit string) (string, error) {
|
||||
return "", t.Gerrit.Tag(ctx, t.Project, version, commit)
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/build/gerrit"
|
||||
"golang.org/x/build/internal/workflow"
|
||||
)
|
||||
|
||||
var flagRunVersionTest = flag.Bool("run-version-test", false, "run version test, which will submit CLs to go.googlesource.com/scratch. Must have a Gerrit cookie in gitcookies.")
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
if !*flagRunVersionTest {
|
||||
t.Skip("Not enabled by flags")
|
||||
}
|
||||
cl := gerrit.NewClient("https://go-review.googlesource.com", gerrit.GitCookiesAuth())
|
||||
tasks := &VersionTasks{
|
||||
Gerrit: &realGerritClient{client: cl},
|
||||
Project: "scratch",
|
||||
}
|
||||
ctx := &workflow.TaskContext{
|
||||
Context: context.Background(),
|
||||
Logger: &testLogger{t},
|
||||
}
|
||||
|
||||
changeID, err := tasks.CreateAutoSubmitVersionCL(ctx, "master", "version string")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = tasks.AwaitCL(ctx, changeID)
|
||||
if strings.Contains(err.Error(), "trybots failed") {
|
||||
t.Logf("Trybots failed, as they usually do: %v. Abandoning CL and ending test.", err)
|
||||
if err := cl.AbandonChange(ctx, changeID, "test is done"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
changeID, err = tasks.Gerrit.CreateAutoSubmitChange(ctx, gerrit.ChangeInput{
|
||||
Project: "scratch",
|
||||
Branch: "master",
|
||||
Subject: "Clean up VERSION",
|
||||
}, map[string]string{"VERSION": ""})
|
||||
if err != nil {
|
||||
t.Fatalf("cleaning up VERSION: %v", err)
|
||||
}
|
||||
if _, err := tasks.AwaitCL(ctx, changeID); err != nil {
|
||||
t.Fatalf("cleaning up VERSION: %v", err)
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче