go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/cli/baserun.go (about)

     1  // Copyright 2019 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cli
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net/http"
    21  	"os"
    22  	"strings"
    23  
    24  	"github.com/maruel/subcommands"
    25  
    26  	"go.chromium.org/luci/auth"
    27  	"go.chromium.org/luci/auth/client/authcli"
    28  	"go.chromium.org/luci/cipd/version"
    29  	"go.chromium.org/luci/common/data/text"
    30  	"go.chromium.org/luci/common/errors"
    31  	"go.chromium.org/luci/common/lhttp"
    32  	"go.chromium.org/luci/grpc/prpc"
    33  	"go.chromium.org/luci/lucictx"
    34  
    35  	pb "go.chromium.org/luci/resultdb/proto/v1"
    36  )
    37  
    38  // ExitCodeCommandFailure indicates that a given command failed due to internal errors
    39  // or invalid input parameters.
    40  const ExitCodeCommandFailure = 123
    41  
    42  // baseCommandRun provides common command run functionality.
    43  // All rdb subcommands must embed it directly or indirectly.
    44  type baseCommandRun struct {
    45  	subcommands.CommandRunBase
    46  	authFlags         authcli.Flags
    47  	host              string
    48  	json              bool
    49  	forceInsecure     bool
    50  	fallbackHost      string
    51  	maxConcurrentRPCs int
    52  
    53  	http        *http.Client
    54  	prpcClient  *prpc.Client
    55  	resultdb    pb.ResultDBClient
    56  	recorder    pb.RecorderClient
    57  	resultdbCtx *lucictx.ResultDB
    58  }
    59  
    60  func (r *baseCommandRun) RegisterGlobalFlags(p Params) {
    61  	r.Flags.StringVar(&r.host, "host", "", text.Doc(`
    62  		Host of the resultdb instance. Overrides the one in LUCI_CONTEXT.
    63  	`))
    64  	r.Flags.BoolVar(&r.forceInsecure, "force-insecure", false, text.Doc(`
    65  		Force HTTP, as opposed to HTTPS.
    66  	`))
    67  	r.Flags.IntVar(&r.maxConcurrentRPCs, "max-concurrent-rpcs", 20, text.Doc(`
    68  		Max concurrent RPCs to the resultdb instance (0 for unlimited).
    69  	`))
    70  	r.authFlags.Register(&r.Flags, p.Auth)
    71  	// Copy the given default to the struct s.t. initClients
    72  	// can use it if needed.
    73  	r.fallbackHost = p.DefaultResultDBHost
    74  }
    75  
    76  func (r *baseCommandRun) RegisterJSONFlag(usage string) {
    77  	r.Flags.BoolVar(&r.json, "json", false, usage)
    78  }
    79  
    80  // initClients validates -host flag and initializes r.httpClient, r.resultdb and
    81  // r.recorder.
    82  func (r *baseCommandRun) initClients(ctx context.Context, loginMode auth.LoginMode) error {
    83  	// Create HTTP Client.
    84  	authOpts, err := r.authFlags.Options()
    85  	if err != nil {
    86  		return err
    87  	}
    88  	r.http, err = auth.NewAuthenticator(ctx, loginMode, authOpts).Client()
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	r.resultdbCtx = lucictx.GetResultDB(ctx)
    94  
    95  	// If no host specified in command line populate from lucictx.
    96  	// If host also not set in lucictx, fall back to r.fallbackHost.
    97  	if r.host == "" {
    98  		if r.resultdbCtx != nil && r.resultdbCtx.Hostname != "" {
    99  			r.host = r.resultdbCtx.Hostname
   100  		} else {
   101  			r.host = r.fallbackHost
   102  		}
   103  	}
   104  
   105  	// Validate -host
   106  	if r.host == "" {
   107  		return fmt.Errorf("a host for resultdb is required")
   108  	}
   109  	if strings.ContainsRune(r.host, '/') {
   110  		return fmt.Errorf("invalid host %q", r.host)
   111  	}
   112  
   113  	if r.maxConcurrentRPCs < 0 {
   114  		return fmt.Errorf("invalid -max-concurrent-rpcs %d", r.maxConcurrentRPCs)
   115  	}
   116  
   117  	// Create clients.
   118  	rpcOpts := prpc.DefaultOptions()
   119  	rpcOpts.Insecure = r.forceInsecure || lhttp.IsLocalHost(r.host)
   120  	info, err := version.GetCurrentVersion()
   121  	if err != nil {
   122  		return err
   123  	}
   124  	rpcOpts.UserAgent = fmt.Sprintf("resultdb CLI, instanceID=%q", info.InstanceID)
   125  	r.prpcClient = &prpc.Client{
   126  		C:                     r.http,
   127  		Host:                  r.host,
   128  		Options:               rpcOpts,
   129  		MaxConcurrentRequests: r.maxConcurrentRPCs,
   130  	}
   131  	r.resultdb = pb.NewResultDBPRPCClient(r.prpcClient)
   132  	r.recorder = pb.NewRecorderPRPCClient(r.prpcClient)
   133  	return nil
   134  }
   135  
   136  func (r *baseCommandRun) validateCurrentInvocation() error {
   137  	if r.resultdbCtx == nil {
   138  		return errors.Reason("resultdb section of LUCI_CONTEXT missing").Err()
   139  	}
   140  
   141  	if r.resultdbCtx.CurrentInvocation.Name == "" {
   142  		return errors.Reason("current invocation name missing from LUCI_CONTEXT").Err()
   143  	}
   144  
   145  	if r.resultdbCtx.CurrentInvocation.UpdateToken == "" {
   146  		return errors.Reason("invocation update token missing from LUCI_CONTEXT").Err()
   147  	}
   148  	return nil
   149  }
   150  
   151  func (r *baseCommandRun) done(err error) int {
   152  	if err != nil {
   153  		fmt.Fprintln(os.Stderr, err)
   154  		return ExitCodeCommandFailure
   155  	}
   156  	return 0
   157  }