go.fuchsia.dev/infra@v0.0.0-20240507153436-9b593402251b/cmd/cl-util/wait_for_cq.go (about) 1 // Copyright 2022 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 "encoding/json" 10 "errors" 11 "fmt" 12 "os" 13 "time" 14 15 "github.com/maruel/subcommands" 16 "go.chromium.org/luci/auth" 17 "go.chromium.org/luci/common/logging" 18 "go.chromium.org/luci/common/logging/gologger" 19 "go.chromium.org/luci/common/retry" 20 "go.chromium.org/luci/common/retry/transient" 21 22 "go.fuchsia.dev/infra/gerrit" 23 ) 24 25 func cmdWaitForCQ(authOpts auth.Options) *subcommands.Command { 26 return &subcommands.Command{ 27 UsageLine: "wait-for-cq -host <gerrit-host> -project <gerrit-project> -change-num <change-num> -json-output <json-output> [-timeout <timeout>]", 28 ShortDesc: "Wait for CQ result for a CL.", 29 LongDesc: "wait for CQ result for a CL. Note this currently only supports CQ dry runs.", 30 CommandRun: func() subcommands.CommandRun { 31 c := &waitForCQRun{} 32 c.Init(authOpts) 33 return c 34 }, 35 } 36 } 37 38 type waitForCQRun struct { 39 commonFlags 40 changeNum int64 41 jsonOutput string 42 timeout time.Duration 43 gerritLabel string 44 } 45 46 func (c *waitForCQRun) Init(defaultAuthOpts auth.Options) { 47 c.commonFlags.Init(defaultAuthOpts) 48 c.Flags.Int64Var(&c.changeNum, "change-num", 0, "Gerrit change number.") 49 c.Flags.StringVar(&c.jsonOutput, "json-output", "", "Path to write CQ status to.") 50 c.Flags.DurationVar(&c.timeout, "timeout", 0, "Wait this long for CQ to finish; indefinite if not set.") 51 c.Flags.StringVar(&c.gerritLabel, "gerrit-label", "", "Gerrit label indicating CQ pass/fail status. If not set, comment text is used to determine pass/fail status.") 52 } 53 54 func (c *waitForCQRun) Parse(a subcommands.Application, args []string) error { 55 if err := c.commonFlags.Parse(); err != nil { 56 return err 57 } 58 if c.changeNum == 0 { 59 return errors.New("-change-num is required") 60 } 61 if c.jsonOutput == "" { 62 return errors.New("-json-output is required") 63 } 64 return nil 65 } 66 67 func (c *waitForCQRun) main(a subcommands.Application) error { 68 ctx := context.Background() 69 ctx = logging.SetLevel(ctx, c.logLevel) 70 ctx = gologger.StdConfig.Use(ctx) 71 authClient, err := newAuthClient(ctx, c.parsedAuthOpts) 72 if err != nil { 73 return err 74 } 75 client, err := gerrit.NewClient(c.gerritHost, c.gerritProject, authClient) 76 if err != nil { 77 return err 78 } 79 // Check for CQ completion once a minute. 80 retryPolicy := transient.Only(func() retry.Iterator { 81 return &retry.ExponentialBackoff{ 82 Limited: retry.Limited{ 83 Delay: time.Minute, 84 Retries: -1, 85 }, 86 Multiplier: 1, 87 } 88 }) 89 var cancel context.CancelFunc 90 if c.timeout > 0 { 91 ctx, cancel = context.WithTimeout(ctx, c.timeout) 92 defer cancel() 93 } 94 if err := retry.Retry(ctx, retryPolicy, func() error { 95 return client.CheckCQCompletion(ctx, c.changeNum, c.gerritLabel) 96 }, nil); err != nil { 97 return err 98 } 99 // Retry transient failures once when looking for CQ pass/fail message. 100 retryPolicy = transient.Only(func() retry.Iterator { 101 return &retry.ExponentialBackoff{ 102 Limited: retry.Limited{ 103 Delay: time.Minute, 104 Retries: 1, 105 }, 106 Multiplier: 1, 107 } 108 }) 109 var passed bool 110 if err := retry.Retry(ctx, retryPolicy, func() error { 111 var err error 112 passed, err = client.CQDryRunPassed(ctx, c.changeNum, c.gerritLabel) 113 return err 114 }, nil); err != nil { 115 return err 116 } 117 118 out := os.Stdout 119 if c.jsonOutput != "-" { 120 out, err = os.Create(c.jsonOutput) 121 if err != nil { 122 return err 123 } 124 defer out.Close() 125 } 126 if err := json.NewEncoder(out).Encode(passed); err != nil { 127 return fmt.Errorf("failed to encode: %w", err) 128 } 129 return nil 130 } 131 132 func (c *waitForCQRun) Run(a subcommands.Application, args []string, env subcommands.Env) int { 133 if err := c.Parse(a, args); err != nil { 134 fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err) 135 return 1 136 } 137 138 if err := c.main(a); err != nil { 139 fmt.Fprintf(a.GetErr(), "%s: %s\n", a.GetName(), err) 140 return 1 141 } 142 return 0 143 }