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  }