Fix FindBuild functionality for annotated git tags.
Recently, COS started using annotated tags for associating a git tag
with a manifest-snapshot commit. Due to this, the FindBuild
functionality broke as annotated tags creates a new git object and
gitiles package didn't provide any API for mapping the git object with
the commitSHA associated with manifest-snapshot.
Now, gerrit package is being used as it provides all the information
such as Object and Revision from which the commitSHA associated with
a commit in manifest-snapshot can be mapped to the git tag i.e.
buildnumber.
Also, handle the cases where the changelist/commitSHA is on main branch.
BUG=b/180129577
TEST=https://paste.googleplex.com/5381390490140672
Change-Id: I30c63a890f5e3092d2c7c6b2a16ebc1ed3d141a9
Reviewed-on: https://cos-review.googlesource.com/c/cos/tools/+/12431
Reviewed-by: Robert Kolchmeyer <rkolchmeyer@google.com>
Tested-by: Vaibhav Rustagi <vaibhavrustagi@google.com>
diff --git a/go.mod b/go.mod
index 091a1b8..2640d7f 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@
require (
cloud.google.com/go v0.57.0
cloud.google.com/go/storage v1.10.0
+ github.com/andygrunwald/go-gerrit v0.0.0-20201231163137-46815e48bfe0
github.com/beevik/etree v1.1.0
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/google/go-cmp v0.5.1
diff --git a/go.sum b/go.sum
index c09867d..029c628 100644
--- a/go.sum
+++ b/go.sum
@@ -41,6 +41,8 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/andygrunwald/go-gerrit v0.0.0-20201231163137-46815e48bfe0 h1:1IlIh8TmY+eAX17cPIUzT4e5R5bQoEngAO5QFcGHbrA=
+github.com/andygrunwald/go-gerrit v0.0.0-20201231163137-46815e48bfe0/go.mod h1:soxaYLbAFToS0OelBriItCts/mtUZOuLBkCk1Xv4ZSo=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -98,6 +100,8 @@
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
+github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
diff --git a/src/cmd/changelogctl/main.go b/src/cmd/changelogctl/main.go
index 211b61d..0838acb 100755
--- a/src/cmd/changelogctl/main.go
+++ b/src/cmd/changelogctl/main.go
@@ -38,9 +38,10 @@
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
- log "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
"go.chromium.org/luci/common/api/gerrit"
+
+ log "github.com/sirupsen/logrus"
)
const (
diff --git a/src/pkg/changelog/changelog.go b/src/pkg/changelog/changelog.go
index f82bee0..3d6fea3 100644
--- a/src/pkg/changelog/changelog.go
+++ b/src/pkg/changelog/changelog.go
@@ -37,6 +37,7 @@
"cos.googlesource.com/cos/tools.git/src/pkg/utils"
"github.com/beevik/etree"
+
log "github.com/sirupsen/logrus"
gitilesApi "go.chromium.org/luci/common/api/gitiles"
gitilesProto "go.chromium.org/luci/common/proto/gitiles"
diff --git a/src/pkg/findbuild/findbuild.go b/src/pkg/findbuild/findbuild.go
index f1117eb..944e98d 100644
--- a/src/pkg/findbuild/findbuild.go
+++ b/src/pkg/findbuild/findbuild.go
@@ -34,8 +34,6 @@
package findbuild
import (
- "context"
-
"fmt"
"net/http"
"regexp"
@@ -46,11 +44,12 @@
"cos.googlesource.com/cos/tools.git/src/pkg/utils"
"github.com/beevik/etree"
- log "github.com/sirupsen/logrus"
- "go.chromium.org/luci/common/api/gerrit"
- gitilesApi "go.chromium.org/luci/common/api/gitiles"
"go.chromium.org/luci/common/proto/git"
"go.chromium.org/luci/common/proto/gitiles"
+
+ gerrit "github.com/andygrunwald/go-gerrit"
+ log "github.com/sirupsen/logrus"
+ gitilesApi "go.chromium.org/luci/common/api/gitiles"
gitilesProto "go.chromium.org/luci/common/proto/gitiles"
)
@@ -163,51 +162,50 @@
}
// queryCL retrieves the list of CLs matching a query from Gerrit
-func queryCL(client *gerrit.Client, clID, instanceURL string) (*gerrit.Change, utils.ChangelogError) {
- log.Debug("Retrieving CL List from Gerrit")
+func queryCL(client *gerrit.Client, clID, instanceURL string) (gerrit.ChangeInfo, utils.ChangelogError) {
+ log.Debugf("Retrieving CL List from Gerrit for clID: %q", clID)
query := queryString(clID)
- queryOptions := gerrit.ChangeQueryParams{
- Query: query,
- N: 1,
- Options: []string{"CURRENT_REVISION"},
- }
- ctx, cancel := context.WithTimeout(context.Background(), requestMaxAge)
- defer cancel()
- clList, _, err := client.ChangeQuery(ctx, queryOptions)
+ queryOptions := &gerrit.QueryChangeOptions{}
+ queryOptions.Query = []string{query}
+ queryOptions.AdditionalFields = []string{"CURRENT_REVISION"}
+ queryOptions.Limit = 1
+
+ clList, _, err := client.Changes.QueryChanges(queryOptions)
if err != nil {
log.Errorf("queryCL: Error retrieving change for input %s:\n%v", clID, err)
httpCode := utils.GerritErrCode(err)
if httpCode == "403" {
- return nil, utils.ForbiddenError
+ return gerrit.ChangeInfo{}, utils.ForbiddenError
} else if httpCode == "400" || httpCode == "404" {
- return nil, utils.CLNotFound(clID)
+ return gerrit.ChangeInfo{}, utils.CLNotFound(clID)
}
- return nil, utils.InternalServerError
+ return gerrit.ChangeInfo{}, utils.InternalServerError
}
- if len(clList) == 0 {
+ if len(*clList) == 0 {
log.Errorf("queryCL: CL with identifier %s not found", clID)
- return nil, utils.CLNotFound(clID)
+ return gerrit.ChangeInfo{}, utils.CLNotFound(clID)
}
- change := clList[0]
- if change.Submitted == "" {
+ change := (*clList)[0]
+ log.Debugf("Found CL: %+v", change)
+ if change.Submitted == nil {
log.Debugf("Provided CL identifier %s maps to an unsubmitted CL", clID)
- return nil, utils.CLNotSubmitted(strconv.Itoa(change.ChangeNumber), instanceURL)
+ return gerrit.ChangeInfo{}, utils.CLNotSubmitted(strconv.Itoa(change.Number), instanceURL)
}
return change, nil
}
-func getCLData(client *gerrit.Client, clID, instanceURL string) (*clData, utils.ChangelogError) {
+func getCLData(clID, instanceURL string, httpClient *http.Client) (*clData, utils.ChangelogError) {
log.Debugf("Retrieving CL data from Gerrit for changeID: %s", clID)
- change, err := queryCL(client, clID, instanceURL)
+ gerritClient, clientErr := gerrit.NewClient(instanceURL, httpClient)
+ if clientErr != nil {
+ log.Errorf("failed to establish Gerrit client for host %s:\n%v", instanceURL, clientErr)
+ return nil, utils.InternalServerError
+ }
+ change, err := queryCL(gerritClient, clID, instanceURL)
if err != nil {
return nil, err
}
log.Debugf("Target CL found with SHA %s on repo %s, branch %s", change.CurrentRevision, change.Project, change.Branch)
- parsedTime, parseErr := time.Parse("2006-01-02 15:04:05.000000000", change.Submitted)
- if parseErr != nil {
- log.Errorf("getTargetData: Error parsing submission time %s for CL %d:\n%v", change.Submitted, change.ChangeNumber, err)
- return nil, utils.InternalServerError
- }
// If a repository has non-conventional branch names, need to convert the
// repository branch name to a release branch name
release := change.Branch
@@ -218,20 +216,26 @@
release = rule.defaultRelease
}
}
+ // In case the branch associated with the change is "main", branch
+ // on the manifest-snapshot will be master.
+ if change.Branch == "main" {
+ release = "master"
+ }
// Strip chromium prefixes
project := change.Project
if matches := crosRepoRe.FindStringSubmatch(project); matches != nil {
project = matches[1]
}
+ submittedTime := *change.Submitted
return &clData{
- CLNum: strconv.Itoa(change.ChangeNumber),
+ CLNum: strconv.Itoa(change.Number),
InstanceURL: instanceURL,
Project: project,
Release: release,
Branch: change.Branch,
Revision: change.CurrentRevision,
- SearchStartRange: parsedTime,
- SearchEndRange: parsedTime.AddDate(0, 0, defaultSearchRange),
+ SearchStartRange: submittedTime.Time,
+ SearchEndRange: submittedTime.Time.AddDate(0, 0, defaultSearchRange),
}, nil
}
@@ -284,20 +288,23 @@
}
// repoTags retrieves all tags belonging to a repository
-func repoTags(client gitilesProto.GitilesClient, repo string) (*gitilesProto.RefsResponse, error) {
+func repoTags(client *gerrit.Client, repo string) (map[string]string, error) {
log.Debugf("Retrieving tags for repository %s", repo)
- request := &gitilesProto.RefsRequest{
- Project: repo,
- RefsPath: "refs/tags",
- }
- ctx, cancel := context.WithTimeout(context.Background(), requestMaxAge)
- defer cancel()
- res, err := client.Refs(ctx, request)
+ tagInfos, _, err := client.Projects.ListTags(repo, &gerrit.ProjectBaseOptions{})
if err != nil {
log.Errorf("error retrieving tags:\n%v", err)
return nil, err
}
- return res, nil
+ tags := make(map[string]string)
+ for _, tagInfo := range *tagInfos {
+ log.Debugf("Tag found: %+v", tagInfo)
+ commitSHA := tagInfo.Revision
+ if tagInfo.Object != "" {
+ commitSHA = tagInfo.Object
+ }
+ tags[tagInfo.Ref] = commitSHA
+ }
+ return tags, nil
}
// candidateBuildNums returns a list of build numbers from a list of possible
@@ -534,14 +541,27 @@
clData.SearchEndRange = clData.SearchStartRange.AddDate(0, 0, defaultSearchRange)
log.Debugf("CL submitted earlier than first build, set search range to starting time from %v to %v", clData.SearchStartRange, clData.SearchEndRange)
}
- tagResp, err := repoTags(gitilesClient, request.ManifestRepo)
+ // Creating a Gerrit client based on manifest-snapshot repository.
+ // The client will be used for finding information associated with
+ // an annotated git tag.
+ instanceURL, err := utils.CreateGerritURL(request.GitilesHost)
+ if err != nil {
+ log.Errorf("failed to create Gerrit URL from Gitiles Host %q: %v", request.GitilesHost, err)
+ return "", utils.InternalServerError
+ }
+ gerritClient, err := gerrit.NewClient(instanceURL, request.HTTPClient)
+ if err != nil {
+ log.Errorf("failed to establish Gerrit client for host %s:\n%v", instanceURL, err)
+ return "", utils.InternalServerError
+ }
+ tagResp, err := repoTags(gerritClient, request.ManifestRepo)
if err != nil {
log.Errorf("failed to retrieve tags for project %s:\n%v", request.ManifestRepo, err)
return "", utils.InternalServerError
}
cache := &iterCache{
GitilesClient: gitilesClient,
- Tags: tagResp.Revisions,
+ Tags: tagResp,
ManifestCommits: manifestCommits,
}
@@ -564,17 +584,12 @@
log.Error("expected non-nil request")
return nil, utils.InternalServerError
}
- gerritClient, err := gerrit.NewClient(request.HTTPClient, request.GerritHost)
- if err != nil {
- log.Errorf("failed to establish Gerrit client for host %s:\n%v", request.GerritHost, err)
- return nil, utils.InternalServerError
- }
gitilesClient, err := gitilesApi.NewRESTClient(request.HTTPClient, request.GitilesHost, true)
if err != nil {
log.Errorf("failed to establish Gitiles client for host %s:\n%v", request.GitilesHost, err)
return nil, utils.InternalServerError
}
- clData, clErr := getCLData(gerritClient, request.CL, request.GerritHost)
+ clData, clErr := getCLData(request.CL, request.GerritHost, request.HTTPClient)
if clErr != nil {
return nil, clErr
}
diff --git a/src/pkg/findbuild/findbuild_test.go b/src/pkg/findbuild/findbuild_test.go
index 96269e3..a7c6a25 100644
--- a/src/pkg/findbuild/findbuild_test.go
+++ b/src/pkg/findbuild/findbuild_test.go
@@ -21,7 +21,6 @@
"testing"
"time"
- "go.chromium.org/luci/common/api/gerrit"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
@@ -34,7 +33,7 @@
)
func getHTTPClient() (*http.Client, error) {
- creds, err := google.FindDefaultCredentials(context.Background(), gerrit.OAuthScope)
+ creds, err := google.FindDefaultCredentials(context.Background(), "https://www.googleapis.com/auth/gerritcodereview")
if err != nil || len(creds.JSON) == 0 {
return nil, fmt.Errorf("no application default credentials found - run `gcloud auth application-default login` and try again")
}
@@ -102,7 +101,7 @@
ShouldFallback: false,
},
"non-existant CL": {
- Change: "9999999999",
+ Change: "9",
GerritHost: externalGerritURL,
GitilesHost: externalGitilesURL,
FallbackGerritHost: externalFallbackGerritURL,
@@ -178,6 +177,14 @@
OutputBuildNum: "12371.1001.0",
ShouldFallback: true,
},
+ "main branch": {
+ Change: "808c2270202fbd79367c4c46b6223e6dfc2d1d01",
+ GerritHost: externalGerritURL,
+ GitilesHost: externalGitilesURL,
+ FallbackGerritHost: externalFallbackGerritURL,
+ ManifestRepo: externalManifestRepo,
+ OutputBuildNum: "16101.0.0",
+ },
}
httpClient, _ := getHTTPClient()
diff --git a/src/pkg/utils/gobutils.go b/src/pkg/utils/gobutils.go
index a154441..f3b28bf 100644
--- a/src/pkg/utils/gobutils.go
+++ b/src/pkg/utils/gobutils.go
@@ -22,10 +22,12 @@
import (
"context"
"fmt"
+ "regexp"
"time"
- log "github.com/sirupsen/logrus"
"go.chromium.org/luci/common/proto/git"
+
+ log "github.com/sirupsen/logrus"
gitilesProto "go.chromium.org/luci/common/proto/gitiles"
)
@@ -124,3 +126,17 @@
log.Debugf("Retrieved %d commits from %s in %s\n", len(allCommits), repo, time.Since(start))
return allCommits, response.NextPageToken != "", nil
}
+
+// CreateGerritURL creates a Gerrit URL from a given
+// Gitiles Host URL. For example: If the given Gitiles
+// Host URL is: https://cos.googlesource.com, then it will
+// return https://cos-review.googlesource.com. In case the
+// Gitiles Host URL is in incorrect format, error is returned.
+func CreateGerritURL(gitilesHostURL string) (string, error) {
+ re := regexp.MustCompile("(.+://)?(.+?).googlesource.com")
+ ss := re.FindStringSubmatch(gitilesHostURL)
+ if len(ss) > 0 {
+ return fmt.Sprintf("https://%s-review.googlesource.com", ss[len(ss)-1]), nil
+ }
+ return "", fmt.Errorf("failed to created Gerrit Host URL from invalid Gitiles Host URL: %q", gitilesHostURL)
+}
diff --git a/src/pkg/utils/gobutils_test.go b/src/pkg/utils/gobutils_test.go
index 8b8013d..5a12948 100644
--- a/src/pkg/utils/gobutils_test.go
+++ b/src/pkg/utils/gobutils_test.go
@@ -211,3 +211,35 @@
})
}
}
+
+func TestCreateGerritURL(t *testing.T) {
+ tests := map[string]struct {
+ input string
+ output string
+ wantErr bool
+ }{
+ "SuccessHTTP": {
+ input: "https://cos.googlesource.com",
+ output: "https://cos-review.googlesource.com",
+ },
+ "SuccessWithoutHTTP": {
+ input: "cos.googlesource.com",
+ output: "https://cos-review.googlesource.com",
+ },
+ "Failure": {
+ input: "https://cos.googleSource.com",
+ wantErr: true,
+ },
+ }
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ gotOutput, err := CreateGerritURL(test.input)
+ if gotErr := err != nil; gotErr != test.wantErr {
+ t.Fatalf("CreateGerritURL(%s) = %v", test.input, err)
+ }
+ if !test.wantErr && gotOutput != test.output {
+ t.Fatalf("CreateGerritURL(%s), got %s, want %s", test.input, gotOutput, test.output)
+ }
+ })
+ }
+}
diff --git a/src/pkg/utils/utils.go b/src/pkg/utils/utils.go
index 7022039..d93efa0 100644
--- a/src/pkg/utils/utils.go
+++ b/src/pkg/utils/utils.go
@@ -16,8 +16,9 @@
"syscall"
"time"
- log "github.com/golang/glog"
"github.com/pkg/errors"
+
+ log "github.com/golang/glog"
)
var (