| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "context" |
| "fmt" |
| |
| "go.chromium.org/luci/auth" |
| "go.chromium.org/luci/common/api/gerrit" |
| "go.chromium.org/luci/common/errors" |
| gerritpb "go.chromium.org/luci/common/proto/gerrit" |
| "go.chromium.org/luci/common/retry/transient" |
| ) |
| |
| const commitQueueLabel = "Commit-Queue" |
| |
| // gerritClientWrapper provides utilities for interacting with a Gerrit project. |
| type gerritClientWrapper struct { |
| client gerritpb.GerritClient |
| project string |
| } |
| |
| // newGerritClient returns an authenticated gerritClientWrapper. |
| func newGerritClient(ctx context.Context, host, project string, authOpts auth.Options) (*gerritClientWrapper, error) { |
| authClient, err := newAuthClient(ctx, authOpts) |
| if err != nil { |
| return nil, err |
| } |
| client, err := gerrit.NewRESTClient(authClient, host, true) |
| if err != nil { |
| return nil, err |
| } |
| return &gerritClientWrapper{client: client, project: project}, nil |
| } |
| |
| // createChange creates an empty Gerrit change. |
| func (c *gerritClientWrapper) createChange(ctx context.Context, subject, baseCommit string) (*gerritpb.ChangeInfo, error) { |
| return c.client.CreateChange(ctx, &gerritpb.CreateChangeRequest{ |
| Project: c.project, |
| Ref: "refs/heads/master", |
| Subject: subject, |
| BaseCommit: baseCommit, |
| }) |
| } |
| |
| // editFile edits a single file for a Gerrit change. |
| func (c *gerritClientWrapper) editFile(ctx context.Context, changeNum int64, filepath, content string) error { |
| _, err := c.client.ChangeEditFileContent(ctx, &gerritpb.ChangeEditFileContentRequest{ |
| Number: changeNum, |
| Project: c.project, |
| FilePath: filepath, |
| Content: []byte(content), |
| }) |
| return err |
| } |
| |
| // publishEdits publishes all pending edits on a Gerrit change. |
| func (c *gerritClientWrapper) publishEdits(ctx context.Context, changeNum int64) error { |
| _, err := c.client.ChangeEditPublish(ctx, &gerritpb.ChangeEditPublishRequest{ |
| Number: changeNum, |
| Project: c.project, |
| }) |
| return err |
| } |
| |
| // setCQLabel sets the CQ+1/2 label on a Gerrit change. |
| func (c *gerritClientWrapper) setCQLabel(ctx context.Context, changeNum int64, dryRun bool) error { |
| cqValue := int32(2) |
| if dryRun { |
| cqValue = int32(1) |
| } |
| changeRes, err := c.client.GetChange(ctx, &gerritpb.GetChangeRequest{ |
| Number: changeNum, |
| Options: []gerritpb.QueryOption{gerritpb.QueryOption_CURRENT_REVISION}, |
| }) |
| if err != nil { |
| return err |
| } |
| reviewRes, err := c.client.SetReview(ctx, &gerritpb.SetReviewRequest{ |
| Number: changeNum, |
| Project: c.project, |
| RevisionId: changeRes.CurrentRevision, |
| Labels: map[string]int32{commitQueueLabel: cqValue}, |
| }) |
| if err != nil { |
| return err |
| } |
| setValue, ok := reviewRes.Labels[commitQueueLabel] |
| if !ok { |
| return fmt.Errorf("%s label was rejected", commitQueueLabel) |
| } |
| if setValue != cqValue { |
| return fmt.Errorf("%s label is %d; expected %d", commitQueueLabel, setValue, cqValue) |
| } |
| return nil |
| } |
| |
| // checkCQCompletion checks if a Gerrit change's CQ label is unset. |
| func (c *gerritClientWrapper) checkCQCompletion(ctx context.Context, changeNum int64) error { |
| changeInfo, err := c.client.GetChange(ctx, &gerritpb.GetChangeRequest{ |
| Number: changeNum, |
| Options: []gerritpb.QueryOption{gerritpb.QueryOption_LABELS}, |
| }) |
| if err != nil { |
| return err |
| } |
| labelInfo, ok := changeInfo.Labels[commitQueueLabel] |
| if !ok { |
| return fmt.Errorf("%s label was not returned", commitQueueLabel) |
| } |
| // The CQ label will be unset eventually, so tag this error as transient. |
| if labelInfo.Value != 0 { |
| return errors.New(fmt.Sprintf("%s label is still set", commitQueueLabel), transient.Tag) |
| } |
| return nil |
| } |