summaryrefslogtreecommitdiff
path: root/vendor/github.com/fullstorydev/grpcurl/cmd
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-05-20 14:28:06 -0600
committermo khan <mo@mokhan.ca>2025-05-23 14:49:19 -0600
commit4beee46dc6c7642316e118a4d3aa51e4b407256e (patch)
tree039bdf57b99061844aeb0fe55ad0bc1c864166af /vendor/github.com/fullstorydev/grpcurl/cmd
parent0ba49bfbde242920d8675a193d7af89420456fc0 (diff)
feat: add external authorization service (authzd) with JWT authentication
- Add new authzd gRPC service implementing Envoy's external authorization API - Integrate JWT authentication filter in Envoy configuration with claim extraction - Update middleware to support both cookie-based and header-based user authentication - Add comprehensive test coverage for authorization service and server - Configure proper service orchestration with authzd, sparkled, and Envoy - Update build system and Docker configuration for multi-service deployment - Add grpcurl tool for gRPC service debugging and testing This enables fine-grained authorization control through Envoy's ext_authz filter while maintaining backward compatibility with existing cookie-based authentication.
Diffstat (limited to 'vendor/github.com/fullstorydev/grpcurl/cmd')
-rw-r--r--vendor/github.com/fullstorydev/grpcurl/cmd/grpcurl/grpcurl.go983
-rw-r--r--vendor/github.com/fullstorydev/grpcurl/cmd/grpcurl/unix.go15
2 files changed, 998 insertions, 0 deletions
diff --git a/vendor/github.com/fullstorydev/grpcurl/cmd/grpcurl/grpcurl.go b/vendor/github.com/fullstorydev/grpcurl/cmd/grpcurl/grpcurl.go
new file mode 100644
index 0000000..b794288
--- /dev/null
+++ b/vendor/github.com/fullstorydev/grpcurl/cmd/grpcurl/grpcurl.go
@@ -0,0 +1,983 @@
+// Command grpcurl makes gRPC requests (a la cURL, but HTTP/2). It can use a supplied descriptor
+// file, protobuf sources, or service reflection to translate JSON or text request data into the
+// appropriate protobuf messages and vice versa for presenting the response contents.
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "io"
+ "math"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/jhump/protoreflect/desc" //lint:ignore SA1019 required to use APIs in other grpcurl package
+ "github.com/jhump/protoreflect/grpcreflect"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/credentials"
+ "google.golang.org/grpc/credentials/alts"
+ "google.golang.org/grpc/keepalive"
+ "google.golang.org/grpc/metadata"
+ "google.golang.org/grpc/status"
+ "google.golang.org/protobuf/types/descriptorpb"
+
+ // Register gzip compressor so compressed responses will work
+ _ "google.golang.org/grpc/encoding/gzip"
+ // Register xds so xds and xds-experimental resolver schemes work
+ _ "google.golang.org/grpc/xds"
+
+ "github.com/fullstorydev/grpcurl"
+)
+
+// To avoid confusion between program error codes and the gRPC response
+// status codes 'Cancelled' and 'Unknown', 1 and 2 respectively,
+// the response status codes emitted use an offset of 64
+const statusCodeOffset = 64
+
+const noVersion = "dev build <no version set>"
+
+var version = noVersion
+
+var (
+ exit = os.Exit
+
+ isUnixSocket func() bool // nil when run on non-unix platform
+
+ flags = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
+
+ help = flags.Bool("help", false, prettify(`
+ Print usage instructions and exit.`))
+ printVersion = flags.Bool("version", false, prettify(`
+ Print version.`))
+
+ plaintext = flags.Bool("plaintext", false, prettify(`
+ Use plain-text HTTP/2 when connecting to server (no TLS).`))
+ insecure = flags.Bool("insecure", false, prettify(`
+ Skip server certificate and domain verification. (NOT SECURE!) Not
+ valid with -plaintext option.`))
+
+ // TLS Options
+ cacert = flags.String("cacert", "", prettify(`
+ File containing trusted root certificates for verifying the server.
+ Ignored if -insecure is specified.`))
+ cert = flags.String("cert", "", prettify(`
+ File containing client certificate (public key), to present to the
+ server. Not valid with -plaintext option. Must also provide -key option.`))
+ key = flags.String("key", "", prettify(`
+ File containing client private key, to present to the server. Not valid
+ with -plaintext option. Must also provide -cert option.`))
+
+ // ALTS Options
+ usealts = flags.Bool("alts", false, prettify(`
+ Use Application Layer Transport Security (ALTS) when connecting to server.`))
+ altsHandshakerServiceAddress = flags.String("alts-handshaker-service", "", prettify(`If set, this server will be used to do the ATLS handshaking.`))
+ altsTargetServiceAccounts multiString
+
+ protoset multiString
+ protoFiles multiString
+ importPaths multiString
+ addlHeaders multiString
+ rpcHeaders multiString
+ reflHeaders multiString
+ expandHeaders = flags.Bool("expand-headers", false, prettify(`
+ If set, headers may use '${NAME}' syntax to reference environment
+ variables. These will be expanded to the actual environment variable
+ value before sending to the server. For example, if there is an
+ environment variable defined like FOO=bar, then a header of
+ 'key: ${FOO}' would expand to 'key: bar'. This applies to -H,
+ -rpc-header, and -reflect-header options. No other expansion/escaping is
+ performed. This can be used to supply credentials/secrets without having
+ to put them in command-line arguments.`))
+ authority = flags.String("authority", "", prettify(`
+ The authoritative name of the remote server. This value is passed as the
+ value of the ":authority" pseudo-header in the HTTP/2 protocol. When TLS
+ is used, this will also be used as the server name when verifying the
+ server's certificate. It defaults to the address that is provided in the
+ positional arguments, or 'localhost' in the case of a unix domain
+ socket.`))
+ userAgent = flags.String("user-agent", "", prettify(`
+ If set, the specified value will be added to the User-Agent header set
+ by the grpc-go library.
+ `))
+ data = flags.String("d", "", prettify(`
+ Data for request contents. If the value is '@' then the request contents
+ are read from stdin. For calls that accept a stream of requests, the
+ contents should include all such request messages concatenated together
+ (possibly delimited; see -format).`))
+ format = flags.String("format", "json", prettify(`
+ The format of request data. The allowed values are 'json' or 'text'. For
+ 'json', the input data must be in JSON format. Multiple request values
+ may be concatenated (messages with a JSON representation other than
+ object must be separated by whitespace, such as a newline). For 'text',
+ the input data must be in the protobuf text format, in which case
+ multiple request values must be separated by the "record separator"
+ ASCII character: 0x1E. The stream should not end in a record separator.
+ If it does, it will be interpreted as a final, blank message after the
+ separator.`))
+ allowUnknownFields = flags.Bool("allow-unknown-fields", false, prettify(`
+ When true, the request contents, if 'json' format is used, allows
+ unknown fields to be present. They will be ignored when parsing
+ the request.`))
+ connectTimeout = flags.Float64("connect-timeout", 0, prettify(`
+ The maximum time, in seconds, to wait for connection to be established.
+ Defaults to 10 seconds.`))
+ formatError = flags.Bool("format-error", false, prettify(`
+ When a non-zero status is returned, format the response using the
+ value set by the -format flag .`))
+ keepaliveTime = flags.Float64("keepalive-time", 0, prettify(`
+ If present, the maximum idle time in seconds, after which a keepalive
+ probe is sent. If the connection remains idle and no keepalive response
+ is received for this same period then the connection is closed and the
+ operation fails.`))
+ maxTime = flags.Float64("max-time", 0, prettify(`
+ The maximum total time the operation can take, in seconds. This sets a
+ timeout on the gRPC context, allowing both client and server to give up
+ after the deadline has past. This is useful for preventing batch jobs
+ that use grpcurl from hanging due to slow or bad network links or due
+ to incorrect stream method usage.`))
+ maxMsgSz = flags.Int("max-msg-sz", 0, prettify(`
+ The maximum encoded size of a response message, in bytes, that grpcurl
+ will accept. If not specified, defaults to 4,194,304 (4 megabytes).`))
+ emitDefaults = flags.Bool("emit-defaults", false, prettify(`
+ Emit default values for JSON-encoded responses.`))
+ protosetOut = flags.String("protoset-out", "", prettify(`
+ The name of a file to be written that will contain a FileDescriptorSet
+ proto. With the list and describe verbs, the listed or described
+ elements and their transitive dependencies will be written to the named
+ file if this option is given. When invoking an RPC and this option is
+ given, the method being invoked and its transitive dependencies will be
+ included in the output file.`))
+ protoOut = flags.String("proto-out-dir", "", prettify(`
+ The name of a directory where the generated .proto files will be written.
+ With the list and describe verbs, the listed or described elements and
+ their transitive dependencies will be written as .proto files in the
+ specified directory if this option is given. When invoking an RPC and
+ this option is given, the method being invoked and its transitive
+ dependencies will be included in the generated .proto files in the
+ output directory.`))
+ msgTemplate = flags.Bool("msg-template", false, prettify(`
+ When describing messages, show a template of input data.`))
+ verbose = flags.Bool("v", false, prettify(`
+ Enable verbose output.`))
+ veryVerbose = flags.Bool("vv", false, prettify(`
+ Enable very verbose output (includes timing data).`))
+ serverName = flags.String("servername", "", prettify(`
+ Override server name when validating TLS certificate. This flag is
+ ignored if -plaintext or -insecure is used.
+ NOTE: Prefer -authority. This flag may be removed in the future. It is
+ an error to use both -authority and -servername (though this will be
+ permitted if they are both set to the same value, to increase backwards
+ compatibility with earlier releases that allowed both to be set).`))
+ reflection = optionalBoolFlag{val: true}
+)
+
+func init() {
+ flags.Var(&addlHeaders, "H", prettify(`
+ Additional headers in 'name: value' format. May specify more than one
+ via multiple flags. These headers will also be included in reflection
+ requests to a server.`))
+ flags.Var(&rpcHeaders, "rpc-header", prettify(`
+ Additional RPC headers in 'name: value' format. May specify more than
+ one via multiple flags. These headers will *only* be used when invoking
+ the requested RPC method. They are excluded from reflection requests.`))
+ flags.Var(&reflHeaders, "reflect-header", prettify(`
+ Additional reflection headers in 'name: value' format. May specify more
+ than one via multiple flags. These headers will *only* be used during
+ reflection requests and will be excluded when invoking the requested RPC
+ method.`))
+ flags.Var(&protoset, "protoset", prettify(`
+ The name of a file containing an encoded FileDescriptorSet. This file's
+ contents will be used to determine the RPC schema instead of querying
+ for it from the remote server via the gRPC reflection API. When set: the
+ 'list' action lists the services found in the given descriptors (vs.
+ those exposed by the remote server), and the 'describe' action describes
+ symbols found in the given descriptors. May specify more than one via
+ multiple -protoset flags. It is an error to use both -protoset and
+ -proto flags.`))
+ flags.Var(&protoFiles, "proto", prettify(`
+ The name of a proto source file. Source files given will be used to
+ determine the RPC schema instead of querying for it from the remote
+ server via the gRPC reflection API. When set: the 'list' action lists
+ the services found in the given files and their imports (vs. those
+ exposed by the remote server), and the 'describe' action describes
+ symbols found in the given files. May specify more than one via multiple
+ -proto flags. Imports will be resolved using the given -import-path
+ flags. Multiple proto files can be specified by specifying multiple
+ -proto flags. It is an error to use both -protoset and -proto flags.`))
+ flags.Var(&importPaths, "import-path", prettify(`
+ The path to a directory from which proto sources can be imported, for
+ use with -proto flags. Multiple import paths can be configured by
+ specifying multiple -import-path flags. Paths will be searched in the
+ order given. If no import paths are given, all files (including all
+ imports) must be provided as -proto flags, and grpcurl will attempt to
+ resolve all import statements from the set of file names given.`))
+ flags.Var(&reflection, "use-reflection", prettify(`
+ When true, server reflection will be used to determine the RPC schema.
+ Defaults to true unless a -proto or -protoset option is provided. If
+ -use-reflection is used in combination with a -proto or -protoset flag,
+ the provided descriptor sources will be used in addition to server
+ reflection to resolve messages and extensions.`))
+ flags.Var(&altsTargetServiceAccounts, "alts-target-service-account", prettify(`
+ The full email address of the service account that the server is
+ expected to be using when ALTS is used. You can specify this option
+ multiple times to indicate multiple allowed service accounts. If the
+ server authenticates with a service account that is not one of the
+ expected accounts, the RPC will not be issued. If no such arguments are
+ provided, no check will be performed, and the RPC will be issued
+ regardless of the server's service account.`))
+}
+
+type multiString []string
+
+func (s *multiString) String() string {
+ return strings.Join(*s, ",")
+}
+
+func (s *multiString) Set(value string) error {
+ *s = append(*s, value)
+ return nil
+}
+
+// Uses a file source as a fallback for resolving symbols and extensions, but
+// only uses the reflection source for listing services
+type compositeSource struct {
+ reflection grpcurl.DescriptorSource
+ file grpcurl.DescriptorSource
+}
+
+func (cs compositeSource) ListServices() ([]string, error) {
+ return cs.reflection.ListServices()
+}
+
+func (cs compositeSource) FindSymbol(fullyQualifiedName string) (desc.Descriptor, error) {
+ d, err := cs.reflection.FindSymbol(fullyQualifiedName)
+ if err == nil {
+ return d, nil
+ }
+ return cs.file.FindSymbol(fullyQualifiedName)
+}
+
+func (cs compositeSource) AllExtensionsForType(typeName string) ([]*desc.FieldDescriptor, error) {
+ exts, err := cs.reflection.AllExtensionsForType(typeName)
+ if err != nil {
+ // On error fall back to file source
+ return cs.file.AllExtensionsForType(typeName)
+ }
+ // Track the tag numbers from the reflection source
+ tags := make(map[int32]bool)
+ for _, ext := range exts {
+ tags[ext.GetNumber()] = true
+ }
+ fileExts, err := cs.file.AllExtensionsForType(typeName)
+ if err != nil {
+ return exts, nil
+ }
+ for _, ext := range fileExts {
+ // Prioritize extensions found via reflection
+ if !tags[ext.GetNumber()] {
+ exts = append(exts, ext)
+ }
+ }
+ return exts, nil
+}
+
+type timingData struct {
+ Title string
+ Start time.Time
+ Value time.Duration
+ Parent *timingData
+ Sub []*timingData
+}
+
+func (d *timingData) Child(title string) *timingData {
+ if d == nil {
+ return nil
+ }
+ child := &timingData{Title: title, Start: time.Now()}
+ d.Sub = append(d.Sub, child)
+ return child
+}
+
+func (d *timingData) Done() {
+ if d == nil {
+ return
+ }
+ if d.Value == 0 {
+ d.Value = time.Since(d.Start)
+ }
+}
+
+func main() {
+ flags.Usage = usage
+ flags.Parse(os.Args[1:])
+ if *help {
+ usage()
+ os.Exit(0)
+ }
+ if *printVersion {
+ fmt.Fprintf(os.Stderr, "%s %s\n", filepath.Base(os.Args[0]), version)
+ os.Exit(0)
+ }
+
+ // default behavior is to use tls
+ usetls := !*plaintext && !*usealts
+
+ // Do extra validation on arguments and figure out what user asked us to do.
+ if *connectTimeout < 0 {
+ fail(nil, "The -connect-timeout argument must not be negative.")
+ }
+ if *keepaliveTime < 0 {
+ fail(nil, "The -keepalive-time argument must not be negative.")
+ }
+ if *maxTime < 0 {
+ fail(nil, "The -max-time argument must not be negative.")
+ }
+ if *maxMsgSz < 0 {
+ fail(nil, "The -max-msg-sz argument must not be negative.")
+ }
+ if *plaintext && *usealts {
+ fail(nil, "The -plaintext and -alts arguments are mutually exclusive.")
+ }
+ if *insecure && !usetls {
+ fail(nil, "The -insecure argument can only be used with TLS.")
+ }
+ if *cert != "" && !usetls {
+ fail(nil, "The -cert argument can only be used with TLS.")
+ }
+ if *key != "" && !usetls {
+ fail(nil, "The -key argument can only be used with TLS.")
+ }
+ if (*key == "") != (*cert == "") {
+ fail(nil, "The -cert and -key arguments must be used together and both be present.")
+ }
+ if *altsHandshakerServiceAddress != "" && !*usealts {
+ fail(nil, "The -alts-handshaker-service argument must be used with the -alts argument.")
+ }
+ if len(altsTargetServiceAccounts) > 0 && !*usealts {
+ fail(nil, "The -alts-target-service-account argument must be used with the -alts argument.")
+ }
+ if *format != "json" && *format != "text" {
+ fail(nil, "The -format option must be 'json' or 'text'.")
+ }
+ if *emitDefaults && *format != "json" {
+ warn("The -emit-defaults is only used when using json format.")
+ }
+
+ args := flags.Args()
+
+ if len(args) == 0 {
+ fail(nil, "Too few arguments.")
+ }
+ var target string
+ if args[0] != "list" && args[0] != "describe" {
+ target = args[0]
+ args = args[1:]
+ }
+
+ if len(args) == 0 {
+ fail(nil, "Too few arguments.")
+ }
+ var list, describe, invoke bool
+ if args[0] == "list" {
+ list = true
+ args = args[1:]
+ } else if args[0] == "describe" {
+ describe = true
+ args = args[1:]
+ } else {
+ invoke = true
+ }
+
+ verbosityLevel := 0
+ if *verbose {
+ verbosityLevel = 1
+ }
+
+ var rootTiming *timingData
+ if *veryVerbose {
+ verbosityLevel = 2
+
+ rootTiming = &timingData{Title: "Timing Data", Start: time.Now()}
+ defer func() {
+ rootTiming.Done()
+ dumpTiming(rootTiming, 0)
+ }()
+ }
+
+ var symbol string
+ if invoke {
+ if len(args) == 0 {
+ fail(nil, "Too few arguments.")
+ }
+ symbol = args[0]
+ args = args[1:]
+ } else {
+ if *data != "" {
+ warn("The -d argument is not used with 'list' or 'describe' verb.")
+ }
+ if len(rpcHeaders) > 0 {
+ warn("The -rpc-header argument is not used with 'list' or 'describe' verb.")
+ }
+ if len(args) > 0 {
+ symbol = args[0]
+ args = args[1:]
+ }
+ }
+
+ if len(args) > 0 {
+ fail(nil, "Too many arguments.")
+ }
+ if invoke && target == "" {
+ fail(nil, "No host:port specified.")
+ }
+ if len(protoset) == 0 && len(protoFiles) == 0 && target == "" {
+ fail(nil, "No host:port specified, no protoset specified, and no proto sources specified.")
+ }
+ if len(protoset) > 0 && len(reflHeaders) > 0 {
+ warn("The -reflect-header argument is not used when -protoset files are used.")
+ }
+ if len(protoset) > 0 && len(protoFiles) > 0 {
+ fail(nil, "Use either -protoset files or -proto files, but not both.")
+ }
+ if len(importPaths) > 0 && len(protoFiles) == 0 {
+ warn("The -import-path argument is not used unless -proto files are used.")
+ }
+ if !reflection.val && len(protoset) == 0 && len(protoFiles) == 0 {
+ fail(nil, "No protoset files or proto files specified and -use-reflection set to false.")
+ }
+
+ // Protoset or protofiles provided and -use-reflection unset
+ if !reflection.set && (len(protoset) > 0 || len(protoFiles) > 0) {
+ reflection.val = false
+ }
+
+ ctx := context.Background()
+ if *maxTime > 0 {
+ timeout := floatSecondsToDuration(*maxTime)
+ var cancel context.CancelFunc
+ ctx, cancel = context.WithTimeout(ctx, timeout)
+ defer cancel()
+ }
+
+ dial := func() *grpc.ClientConn {
+ dialTiming := rootTiming.Child("Dial")
+ defer dialTiming.Done()
+ dialTime := 10 * time.Second
+ if *connectTimeout > 0 {
+ dialTime = floatSecondsToDuration(*connectTimeout)
+ }
+ ctx, cancel := context.WithTimeout(ctx, dialTime)
+ defer cancel()
+ var opts []grpc.DialOption
+ if *keepaliveTime > 0 {
+ timeout := floatSecondsToDuration(*keepaliveTime)
+ opts = append(opts, grpc.WithKeepaliveParams(keepalive.ClientParameters{
+ Time: timeout,
+ Timeout: timeout,
+ }))
+ }
+ if *maxMsgSz > 0 {
+ opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(*maxMsgSz)))
+ }
+ network := "tcp"
+ if isUnixSocket != nil && isUnixSocket() {
+ network = "unix"
+ if *authority == "" {
+ *authority = "localhost"
+ }
+ }
+ var creds credentials.TransportCredentials
+ if *plaintext {
+ if *authority != "" {
+ opts = append(opts, grpc.WithAuthority(*authority))
+ }
+ } else if *usealts {
+ clientOptions := alts.DefaultClientOptions()
+ if len(altsTargetServiceAccounts) > 0 {
+ clientOptions.TargetServiceAccounts = altsTargetServiceAccounts
+ }
+ if *altsHandshakerServiceAddress != "" {
+ clientOptions.HandshakerServiceAddress = *altsHandshakerServiceAddress
+ }
+ creds = alts.NewClientCreds(clientOptions)
+ } else if usetls {
+ tlsTiming := dialTiming.Child("TLS Setup")
+ defer tlsTiming.Done()
+
+ tlsConf, err := grpcurl.ClientTLSConfig(*insecure, *cacert, *cert, *key)
+ if err != nil {
+ fail(err, "Failed to create TLS config")
+ }
+
+ sslKeylogFile := os.Getenv("SSLKEYLOGFILE")
+ if sslKeylogFile != "" {
+ w, err := os.OpenFile(sslKeylogFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
+ if err != nil {
+ fail(err, "Could not open SSLKEYLOGFILE %s", sslKeylogFile)
+ }
+ tlsConf.KeyLogWriter = w
+ }
+
+ creds = credentials.NewTLS(tlsConf)
+
+ // can use either -servername or -authority; but not both
+ if *serverName != "" && *authority != "" {
+ if *serverName == *authority {
+ warn("Both -servername and -authority are present; prefer only -authority.")
+ } else {
+ fail(nil, "Cannot specify different values for -servername and -authority.")
+ }
+ }
+ overrideName := *serverName
+ if overrideName == "" {
+ overrideName = *authority
+ }
+
+ if overrideName != "" {
+ opts = append(opts, grpc.WithAuthority(overrideName))
+ }
+ tlsTiming.Done()
+ } else {
+ panic("Should have defaulted to use TLS.")
+ }
+
+ grpcurlUA := "grpcurl/" + version
+ if version == noVersion {
+ grpcurlUA = "grpcurl/dev-build (no version set)"
+ }
+ if *userAgent != "" {
+ grpcurlUA = *userAgent + " " + grpcurlUA
+ }
+ opts = append(opts, grpc.WithUserAgent(grpcurlUA))
+
+ blockingDialTiming := dialTiming.Child("BlockingDial")
+ defer blockingDialTiming.Done()
+ cc, err := grpcurl.BlockingDial(ctx, network, target, creds, opts...)
+ if err != nil {
+ fail(err, "Failed to dial target host %q", target)
+ }
+ return cc
+ }
+ printFormattedStatus := func(w io.Writer, stat *status.Status, formatter grpcurl.Formatter) {
+ formattedStatus, err := formatter(stat.Proto())
+ if err != nil {
+ fmt.Fprintf(w, "ERROR: %v", err.Error())
+ }
+ fmt.Fprint(w, formattedStatus)
+ }
+
+ if *expandHeaders {
+ var err error
+ addlHeaders, err = grpcurl.ExpandHeaders(addlHeaders)
+ if err != nil {
+ fail(err, "Failed to expand additional headers")
+ }
+ rpcHeaders, err = grpcurl.ExpandHeaders(rpcHeaders)
+ if err != nil {
+ fail(err, "Failed to expand rpc headers")
+ }
+ reflHeaders, err = grpcurl.ExpandHeaders(reflHeaders)
+ if err != nil {
+ fail(err, "Failed to expand reflection headers")
+ }
+ }
+
+ var cc *grpc.ClientConn
+ var descSource grpcurl.DescriptorSource
+ var refClient *grpcreflect.Client
+ var fileSource grpcurl.DescriptorSource
+ if len(protoset) > 0 {
+ var err error
+ fileSource, err = grpcurl.DescriptorSourceFromProtoSets(protoset...)
+ if err != nil {
+ fail(err, "Failed to process proto descriptor sets.")
+ }
+ } else if len(protoFiles) > 0 {
+ var err error
+ fileSource, err = grpcurl.DescriptorSourceFromProtoFiles(importPaths, protoFiles...)
+ if err != nil {
+ fail(err, "Failed to process proto source files.")
+ }
+ }
+ if reflection.val {
+ md := grpcurl.MetadataFromHeaders(append(addlHeaders, reflHeaders...))
+ refCtx := metadata.NewOutgoingContext(ctx, md)
+ cc = dial()
+ refClient = grpcreflect.NewClientAuto(refCtx, cc)
+ refClient.AllowMissingFileDescriptors()
+ reflSource := grpcurl.DescriptorSourceFromServer(ctx, refClient)
+ if fileSource != nil {
+ descSource = compositeSource{reflSource, fileSource}
+ } else {
+ descSource = reflSource
+ }
+ } else {
+ descSource = fileSource
+ }
+
+ // arrange for the RPCs to be cleanly shutdown
+ reset := func() {
+ if refClient != nil {
+ refClient.Reset()
+ refClient = nil
+ }
+ if cc != nil {
+ cc.Close()
+ cc = nil
+ }
+ }
+ defer reset()
+ exit = func(code int) {
+ // since defers aren't run by os.Exit...
+ reset()
+ os.Exit(code)
+ }
+
+ if list {
+ if symbol == "" {
+ svcs, err := grpcurl.ListServices(descSource)
+ if err != nil {
+ fail(err, "Failed to list services")
+ }
+ if len(svcs) == 0 {
+ fmt.Println("(No services)")
+ } else {
+ for _, svc := range svcs {
+ fmt.Printf("%s\n", svc)
+ }
+ }
+ if err := writeProtoset(descSource, svcs...); err != nil {
+ fail(err, "Failed to write protoset to %s", *protosetOut)
+ }
+ if err := writeProtos(descSource, svcs...); err != nil {
+ fail(err, "Failed to write protos to %s", *protoOut)
+ }
+ } else {
+ methods, err := grpcurl.ListMethods(descSource, symbol)
+ if err != nil {
+ fail(err, "Failed to list methods for service %q", symbol)
+ }
+ if len(methods) == 0 {
+ fmt.Println("(No methods)") // probably unlikely
+ } else {
+ for _, m := range methods {
+ fmt.Printf("%s\n", m)
+ }
+ }
+ if err := writeProtoset(descSource, symbol); err != nil {
+ fail(err, "Failed to write protoset to %s", *protosetOut)
+ }
+ if err := writeProtos(descSource, symbol); err != nil {
+ fail(err, "Failed to write protos to %s", *protoOut)
+ }
+ }
+
+ } else if describe {
+ var symbols []string
+ if symbol != "" {
+ symbols = []string{symbol}
+ } else {
+ // if no symbol given, describe all exposed services
+ svcs, err := descSource.ListServices()
+ if err != nil {
+ fail(err, "Failed to list services")
+ }
+ if len(svcs) == 0 {
+ fmt.Println("Server returned an empty list of exposed services")
+ }
+ symbols = svcs
+ }
+ for _, s := range symbols {
+ if s[0] == '.' {
+ s = s[1:]
+ }
+
+ dsc, err := descSource.FindSymbol(s)
+ if err != nil {
+ fail(err, "Failed to resolve symbol %q", s)
+ }
+
+ fqn := dsc.GetFullyQualifiedName()
+ var elementType string
+ switch d := dsc.(type) {
+ case *desc.MessageDescriptor:
+ elementType = "a message"
+ parent, ok := d.GetParent().(*desc.MessageDescriptor)
+ if ok {
+ if d.IsMapEntry() {
+ for _, f := range parent.GetFields() {
+ if f.IsMap() && f.GetMessageType() == d {
+ // found it: describe the map field instead
+ elementType = "the entry type for a map field"
+ dsc = f
+ break
+ }
+ }
+ } else {
+ // see if it's a group
+ for _, f := range parent.GetFields() {
+ if f.GetType() == descriptorpb.FieldDescriptorProto_TYPE_GROUP && f.GetMessageType() == d {
+ // found it: describe the map field instead
+ elementType = "the type of a group field"
+ dsc = f
+ break
+ }
+ }
+ }
+ }
+ case *desc.FieldDescriptor:
+ elementType = "a field"
+ if d.GetType() == descriptorpb.FieldDescriptorProto_TYPE_GROUP {
+ elementType = "a group field"
+ } else if d.IsExtension() {
+ elementType = "an extension"
+ }
+ case *desc.OneOfDescriptor:
+ elementType = "a one-of"
+ case *desc.EnumDescriptor:
+ elementType = "an enum"
+ case *desc.EnumValueDescriptor:
+ elementType = "an enum value"
+ case *desc.ServiceDescriptor:
+ elementType = "a service"
+ case *desc.MethodDescriptor:
+ elementType = "a method"
+ default:
+ err = fmt.Errorf("descriptor has unrecognized type %T", dsc)
+ fail(err, "Failed to describe symbol %q", s)
+ }
+
+ txt, err := grpcurl.GetDescriptorText(dsc, descSource)
+ if err != nil {
+ fail(err, "Failed to describe symbol %q", s)
+ }
+ fmt.Printf("%s is %s:\n", fqn, elementType)
+ fmt.Println(txt)
+
+ if dsc, ok := dsc.(*desc.MessageDescriptor); ok && *msgTemplate {
+ // for messages, also show a template in JSON, to make it easier to
+ // create a request to invoke an RPC
+ tmpl := grpcurl.MakeTemplate(dsc)
+ options := grpcurl.FormatOptions{EmitJSONDefaultFields: true}
+ _, formatter, err := grpcurl.RequestParserAndFormatter(grpcurl.Format(*format), descSource, nil, options)
+ if err != nil {
+ fail(err, "Failed to construct formatter for %q", *format)
+ }
+ str, err := formatter(tmpl)
+ if err != nil {
+ fail(err, "Failed to print template for message %s", s)
+ }
+ fmt.Println("\nMessage template:")
+ fmt.Println(str)
+ }
+ }
+ if err := writeProtoset(descSource, symbols...); err != nil {
+ fail(err, "Failed to write protoset to %s", *protosetOut)
+ }
+ if err := writeProtos(descSource, symbol); err != nil {
+ fail(err, "Failed to write protos to %s", *protoOut)
+ }
+
+ } else {
+ // Invoke an RPC
+ if cc == nil {
+ cc = dial()
+ }
+ var in io.Reader
+ if *data == "@" {
+ in = os.Stdin
+ } else {
+ in = strings.NewReader(*data)
+ }
+
+ // if not verbose output, then also include record delimiters
+ // between each message, so output could potentially be piped
+ // to another grpcurl process
+ includeSeparators := verbosityLevel == 0
+ options := grpcurl.FormatOptions{
+ EmitJSONDefaultFields: *emitDefaults,
+ IncludeTextSeparator: includeSeparators,
+ AllowUnknownFields: *allowUnknownFields,
+ }
+ rf, formatter, err := grpcurl.RequestParserAndFormatter(grpcurl.Format(*format), descSource, in, options)
+ if err != nil {
+ fail(err, "Failed to construct request parser and formatter for %q", *format)
+ }
+ h := &grpcurl.DefaultEventHandler{
+ Out: os.Stdout,
+ Formatter: formatter,
+ VerbosityLevel: verbosityLevel,
+ }
+
+ invokeTiming := rootTiming.Child("InvokeRPC")
+ err = grpcurl.InvokeRPC(ctx, descSource, cc, symbol, append(addlHeaders, rpcHeaders...), h, rf.Next)
+ invokeTiming.Done()
+ if err != nil {
+ if errStatus, ok := status.FromError(err); ok && *formatError {
+ h.Status = errStatus
+ } else {
+ fail(err, "Error invoking method %q", symbol)
+ }
+ }
+ reqSuffix := ""
+ respSuffix := ""
+ reqCount := rf.NumRequests()
+ if reqCount != 1 {
+ reqSuffix = "s"
+ }
+ if h.NumResponses != 1 {
+ respSuffix = "s"
+ }
+ if verbosityLevel > 0 {
+ fmt.Printf("Sent %d request%s and received %d response%s\n", reqCount, reqSuffix, h.NumResponses, respSuffix)
+ }
+ if h.Status.Code() != codes.OK {
+ if *formatError {
+ printFormattedStatus(os.Stderr, h.Status, formatter)
+ } else {
+ grpcurl.PrintStatus(os.Stderr, h.Status, formatter)
+ }
+ exit(statusCodeOffset + int(h.Status.Code()))
+ }
+ }
+}
+
+func dumpTiming(td *timingData, lvl int) {
+ ind := ""
+ for x := 0; x < lvl; x++ {
+ ind += " "
+ }
+ fmt.Printf("%s%s: %s\n", ind, td.Title, td.Value)
+ for _, sd := range td.Sub {
+ dumpTiming(sd, lvl+1)
+ }
+}
+
+func usage() {
+ fmt.Fprintf(os.Stderr, `Usage:
+ %s [flags] [address] [list|describe] [symbol]
+
+The 'address' is only optional when used with 'list' or 'describe' and a
+protoset or proto flag is provided.
+
+If 'list' is indicated, the symbol (if present) should be a fully-qualified
+service name. If present, all methods of that service are listed. If not
+present, all exposed services are listed, or all services defined in protosets.
+
+If 'describe' is indicated, the descriptor for the given symbol is shown. The
+symbol should be a fully-qualified service, enum, or message name. If no symbol
+is given then the descriptors for all exposed or known services are shown.
+
+If neither verb is present, the symbol must be a fully-qualified method name in
+'service/method' or 'service.method' format. In this case, the request body will
+be used to invoke the named method. If no body is given but one is required
+(i.e. the method is unary or server-streaming), an empty instance of the
+method's request type will be sent.
+
+The address will typically be in the form "host:port" where host can be an IP
+address or a hostname and port is a numeric port or service name. If an IPv6
+address is given, it must be surrounded by brackets, like "[2001:db8::1]". For
+Unix variants, if a -unix=true flag is present, then the address must be the
+path to the domain socket.
+
+Available flags:
+`, os.Args[0])
+ flags.PrintDefaults()
+}
+
+func prettify(docString string) string {
+ parts := strings.Split(docString, "\n")
+
+ // cull empty lines and also remove trailing and leading spaces
+ // from each line in the doc string
+ j := 0
+ for _, part := range parts {
+ part = strings.TrimSpace(part)
+ if part == "" {
+ continue
+ }
+ parts[j] = part
+ j++
+ }
+
+ return strings.Join(parts[:j], "\n")
+}
+
+func warn(msg string, args ...interface{}) {
+ msg = fmt.Sprintf("Warning: %s\n", msg)
+ fmt.Fprintf(os.Stderr, msg, args...)
+}
+
+func fail(err error, msg string, args ...interface{}) {
+ if err != nil {
+ msg += ": %v"
+ args = append(args, err)
+ }
+ fmt.Fprintf(os.Stderr, msg, args...)
+ fmt.Fprintln(os.Stderr)
+ if err != nil {
+ exit(1)
+ } else {
+ // nil error means it was CLI usage issue
+ fmt.Fprintf(os.Stderr, "Try '%s -help' for more details.\n", os.Args[0])
+ exit(2)
+ }
+}
+
+func writeProtoset(descSource grpcurl.DescriptorSource, symbols ...string) error {
+ if *protosetOut == "" {
+ return nil
+ }
+ f, err := os.Create(*protosetOut)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ return grpcurl.WriteProtoset(f, descSource, symbols...)
+}
+
+func writeProtos(descSource grpcurl.DescriptorSource, symbols ...string) error {
+ if *protoOut == "" {
+ return nil
+ }
+ return grpcurl.WriteProtoFiles(*protoOut, descSource, symbols...)
+}
+
+type optionalBoolFlag struct {
+ set, val bool
+}
+
+func (f *optionalBoolFlag) String() string {
+ if !f.set {
+ return "unset"
+ }
+ return strconv.FormatBool(f.val)
+}
+
+func (f *optionalBoolFlag) Set(s string) error {
+ v, err := strconv.ParseBool(s)
+ if err != nil {
+ return err
+ }
+ f.set = true
+ f.val = v
+ return nil
+}
+
+func (f *optionalBoolFlag) IsBoolFlag() bool {
+ return true
+}
+
+func floatSecondsToDuration(seconds float64) time.Duration {
+ durationFloat := seconds * float64(time.Second)
+ if durationFloat > math.MaxInt64 {
+ // Avoid overflow
+ return math.MaxInt64
+ }
+ return time.Duration(durationFloat)
+}
diff --git a/vendor/github.com/fullstorydev/grpcurl/cmd/grpcurl/unix.go b/vendor/github.com/fullstorydev/grpcurl/cmd/grpcurl/unix.go
new file mode 100644
index 0000000..cae4bed
--- /dev/null
+++ b/vendor/github.com/fullstorydev/grpcurl/cmd/grpcurl/unix.go
@@ -0,0 +1,15 @@
+//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris windows
+
+package main
+
+var (
+ unix = flags.Bool("unix", false, prettify(`
+ Indicates that the server address is the path to a Unix domain socket.`))
+)
+
+func init() {
+ isUnixSocket = func() bool {
+ return *unix
+ }
+}