blob: 9a181e119c64cbbf37c9ef93172d8985ef5740ab [file] [log] [blame]
// 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"
"errors"
"fmt"
"time"
"github.com/maruel/subcommands"
"go.chromium.org/luci/auth"
"go.chromium.org/luci/common/retry"
"go.chromium.org/luci/common/retry/transient"
"go.fuchsia.dev/infra/gerrit"
)
func cmdTriggerCQ(authOpts auth.Options) *subcommands.Command {
return &subcommands.Command{
UsageLine: "trigger-cq -host <gerrit-host> -project <gerrit-project> -change-num <change-num> [-wait] [-timeout <timeout>] [-dryrun]",
ShortDesc: "Trigger CQ on a CL.",
LongDesc: "Trigger CQ on a CL.",
CommandRun: func() subcommands.CommandRun {
c := &triggerCQRun{}
c.Init(authOpts)
return c
},
}
}
type triggerCQRun struct {
commonFlags
changeNum int64
wait bool
timeout time.Duration
dryRun bool
}
func (c *triggerCQRun) Init(defaultAuthOpts auth.Options) {
c.commonFlags.Init(defaultAuthOpts)
c.Flags.Int64Var(&c.changeNum, "change-num", 0, "Gerrit change number.")
c.Flags.BoolVar(&c.wait, "wait", false, "If set, wait for CQ to complete.")
c.Flags.DurationVar(&c.timeout, "timeout", 0, "Wait this long for CQ to finish; indefinite if not set.")
c.Flags.BoolVar(&c.dryRun, "dryrun", false, "If set, apply CQ+1; otherwise apply CQ+2.")
}
func (c *triggerCQRun) Parse(a subcommands.Application, args []string) error {
if err := c.commonFlags.Parse(); err != nil {
return err
}
if c.changeNum == 0 {
return errors.New("-change-num is required")
}
return nil
}
func (c *triggerCQRun) main(a subcommands.Application) error {
ctx := context.Background()
authClient, err := newAuthClient(ctx, c.parsedAuthOpts)
if err != nil {
return err
}
client, err := gerrit.NewClient(c.gerritHost, c.gerritProject, authClient)
if err != nil {
return err
}
// Retry transient failures when setting CQ label.
setLabelRetryPolicy := transient.Only(func() retry.Iterator {
return &retry.ExponentialBackoff{
Limited: retry.Limited{
Delay: 20 * time.Second,
Retries: 3,
},
Multiplier: 1,
}
})
if err := retry.Retry(ctx, setLabelRetryPolicy, func() error {
return client.SetCQLabel(ctx, c.changeNum, c.dryRun)
}, nil); err != nil {
return err
}
if !c.wait {
return nil
}
// Check for CQ completion once a minute.
retryPolicy := transient.Only(func() retry.Iterator {
return &retry.ExponentialBackoff{
Limited: retry.Limited{
Delay: time.Minute,
Retries: -1,
},
Multiplier: 1,
}
})
var cancel context.CancelFunc
if c.timeout > 0 {
ctx, cancel = context.WithTimeout(ctx, c.timeout)
defer cancel()
}
return retry.Retry(ctx, retryPolicy, func() error {
return client.CheckCQCompletion(ctx, c.changeNum)
}, nil)
}
func (c *triggerCQRun) Run(a subcommands.Application, args []string, env subcommands.Env) int {
if err := c.Parse(a, args); err != nil {
fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err)
return 1
}
if err := c.main(a); err != nil {
fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err)
return 1
}
return 0
}