go.fuchsia.dev/infra@v0.0.0-20240507153436-9b593402251b/cmd/cl-util/trigger_cq.go (about) 1 // Copyright 2020 The Fuchsia Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package main 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "time" 12 13 "github.com/maruel/subcommands" 14 "go.chromium.org/luci/auth" 15 "go.chromium.org/luci/common/logging" 16 "go.chromium.org/luci/common/logging/gologger" 17 "go.chromium.org/luci/common/retry" 18 "go.chromium.org/luci/common/retry/transient" 19 20 "go.fuchsia.dev/infra/gerrit" 21 ) 22 23 func cmdTriggerCQ(authOpts auth.Options) *subcommands.Command { 24 return &subcommands.Command{ 25 UsageLine: "trigger-cq -host <gerrit-host> -project <gerrit-project> -change-num <change-num> [-wait] [-timeout <timeout>] [-dryrun]", 26 ShortDesc: "Trigger CQ on a CL.", 27 LongDesc: "Trigger CQ on a CL.", 28 CommandRun: func() subcommands.CommandRun { 29 c := &triggerCQRun{} 30 c.Init(authOpts) 31 return c 32 }, 33 } 34 } 35 36 type triggerCQRun struct { 37 commonFlags 38 changeNum int64 39 wait bool 40 timeout time.Duration 41 dryRun bool 42 } 43 44 func (c *triggerCQRun) Init(defaultAuthOpts auth.Options) { 45 c.commonFlags.Init(defaultAuthOpts) 46 c.Flags.Int64Var(&c.changeNum, "change-num", 0, "Gerrit change number.") 47 c.Flags.BoolVar(&c.wait, "wait", false, "If set, wait for CQ to complete.") 48 c.Flags.DurationVar(&c.timeout, "timeout", 0, "Wait this long for CQ to finish; indefinite if not set.") 49 c.Flags.BoolVar(&c.dryRun, "dryrun", false, "If set, apply CQ+1; otherwise apply CQ+2.") 50 } 51 52 func (c *triggerCQRun) Parse(a subcommands.Application, args []string) error { 53 if err := c.commonFlags.Parse(); err != nil { 54 return err 55 } 56 if c.changeNum == 0 { 57 return errors.New("-change-num is required") 58 } 59 return nil 60 } 61 62 func (c *triggerCQRun) main(a subcommands.Application) error { 63 ctx := context.Background() 64 ctx = logging.SetLevel(ctx, c.logLevel) 65 ctx = gologger.StdConfig.Use(ctx) 66 authClient, err := newAuthClient(ctx, c.parsedAuthOpts) 67 if err != nil { 68 return err 69 } 70 client, err := gerrit.NewClient(c.gerritHost, c.gerritProject, authClient) 71 if err != nil { 72 return err 73 } 74 // Retry transient failures when setting CQ label. 75 setLabelRetryPolicy := transient.Only(func() retry.Iterator { 76 return &retry.ExponentialBackoff{ 77 Limited: retry.Limited{ 78 Delay: 20 * time.Second, 79 Retries: 3, 80 }, 81 Multiplier: 1, 82 } 83 }) 84 if err := retry.Retry(ctx, setLabelRetryPolicy, func() error { 85 return client.SetCQLabel(ctx, c.changeNum, c.dryRun) 86 }, nil); err != nil { 87 return err 88 } 89 if !c.wait { 90 return nil 91 } 92 // Check for CQ completion once a minute. 93 retryPolicy := transient.Only(func() retry.Iterator { 94 return &retry.ExponentialBackoff{ 95 Limited: retry.Limited{ 96 Delay: time.Minute, 97 Retries: -1, 98 }, 99 Multiplier: 1, 100 } 101 }) 102 var cancel context.CancelFunc 103 if c.timeout > 0 { 104 ctx, cancel = context.WithTimeout(ctx, c.timeout) 105 defer cancel() 106 } 107 return retry.Retry(ctx, retryPolicy, func() error { 108 return client.CheckCQCompletion(ctx, c.changeNum, "") 109 }, nil) 110 } 111 112 func (c *triggerCQRun) Run(a subcommands.Application, args []string, env subcommands.Env) int { 113 if err := c.Parse(a, args); err != nil { 114 fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err) 115 return 1 116 } 117 118 if err := c.main(a); err != nil { 119 fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err) 120 return 1 121 } 122 return 0 123 }