go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/grpc/cmd/prpc/main.go (about) 1 // Copyright 2016 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 main 16 17 import ( 18 "context" 19 "fmt" 20 "os" 21 22 "github.com/maruel/subcommands" 23 24 "go.chromium.org/luci/auth" 25 "go.chromium.org/luci/auth/client/authcli" 26 "go.chromium.org/luci/common/cli" 27 "go.chromium.org/luci/common/lhttp" 28 "go.chromium.org/luci/common/logging" 29 "go.chromium.org/luci/common/logging/gologger" 30 "go.chromium.org/luci/grpc/prpc" 31 32 "go.chromium.org/luci/hardcoded/chromeinfra" 33 ) 34 35 var logCfg = gologger.LoggerConfig{ 36 Format: `%{message}`, 37 Out: os.Stderr, 38 } 39 40 // exit codes: 41 const ( 42 ecInvalidCommandLine = -iota 43 ecAuthenticatedClientError 44 ecOtherError 45 ) 46 47 type exitCode struct { 48 err error 49 code int 50 } 51 52 func (e *exitCode) Error() string { return e.err.Error() } 53 54 // cmdRun is a base of all prpc subcommands. 55 // It defines some common flags, such as logging and auth, and useful methods. 56 type cmdRun struct { 57 subcommands.CommandRunBase 58 verbose bool 59 forceInsecure bool 60 auth authcli.Flags 61 } 62 63 // ModifyContext implements cli.ContextModificator. 64 func (r *cmdRun) ModifyContext(ctx context.Context) context.Context { 65 if r.verbose { 66 ctx = logging.SetLevel(ctx, logging.Debug) 67 } 68 return ctx 69 } 70 71 // registerBaseFlags registers common flags used by all subcommands. 72 func (r *cmdRun) registerBaseFlags(defaultAuthOpts auth.Options) { 73 r.Flags.BoolVar(&r.verbose, "verbose", false, "Enable more logging.") 74 r.Flags.BoolVar(&r.forceInsecure, "force-insecure", false, "Force HTTP instead of HTTPS") 75 r.auth.Register(&r.Flags, defaultAuthOpts) 76 r.auth.RegisterIDTokenFlags(&r.Flags) 77 } 78 79 func (r *cmdRun) authenticatedClient(ctx context.Context, host string) (*prpc.Client, error) { 80 authOpts, err := r.auth.Options() 81 if err != nil { 82 return nil, err 83 } 84 if authOpts.UseIDTokens && authOpts.Audience == "" { 85 authOpts.Audience = "https://" + host 86 } 87 a := auth.NewAuthenticator(ctx, auth.OptionalLogin, authOpts) 88 httpClient, err := a.Client() 89 if err != nil { 90 return nil, err 91 } 92 93 client := prpc.Client{ 94 C: httpClient, 95 Host: host, 96 Options: prpc.DefaultOptions(), 97 } 98 client.Options.Insecure = r.forceInsecure || lhttp.IsLocalHost(host) 99 return &client, nil 100 } 101 102 // argErr prints an err and usage to stderr and returns an exit code. 103 func (r *cmdRun) argErr(shortDesc, usageLine, format string, a ...any) int { 104 if format != "" { 105 fmt.Fprintf(os.Stderr, format+"\n", a...) 106 } 107 fmt.Fprintln(os.Stderr, shortDesc) 108 fmt.Fprintln(os.Stderr, usageLine) 109 fmt.Fprintln(os.Stderr, "\nFlags:") 110 r.Flags.PrintDefaults() 111 return ecInvalidCommandLine 112 } 113 114 // done prints err to stderr if it is not nil and returns an exit code. 115 func (r *cmdRun) done(err error) int { 116 if err != nil { 117 fmt.Fprintln(os.Stderr, err) 118 if err, ok := err.(*exitCode); ok { 119 return err.code 120 } 121 return ecOtherError 122 } 123 return 0 124 } 125 126 func getApplication(defaultAuthOpts auth.Options) *cli.Application { 127 return &cli.Application{ 128 Name: "prpc", 129 Title: "Provisional Remote Procedure Call CLI", 130 Context: func(ctx context.Context) context.Context { 131 return logCfg.Use(ctx) 132 }, 133 Commands: []*subcommands.Command{ 134 cmdCall(defaultAuthOpts), 135 cmdShow(defaultAuthOpts), 136 137 { /* spacer */ }, 138 139 authcli.SubcommandLogin(defaultAuthOpts, "login", false), 140 authcli.SubcommandLogout(defaultAuthOpts, "logout", false), 141 142 { /* spacer */ }, 143 144 subcommands.CmdHelp, 145 }, 146 } 147 } 148 149 func main() { 150 app := getApplication(chromeinfra.DefaultAuthOptions()) 151 os.Exit(subcommands.Run(app, os.Args[1:])) 152 }