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 }