add WriteProtoset method, for creating protoset during symbol resolution

This commit is contained in:
Josh Humphries 2019-09-29 16:09:12 -04:00
parent 9248ea0963
commit 252d61c7a9
2 changed files with 87 additions and 7 deletions

View File

@ -59,13 +59,14 @@ var (
rpcHeaders multiString rpcHeaders multiString
reflHeaders multiString reflHeaders multiString
expandHeaders = flags.Bool("expand-headers", false, prettify(` expandHeaders = flags.Bool("expand-headers", false, prettify(`
If set, headers may use '${NAME}' syntax to reference environment variables. If set, headers may use '${NAME}' syntax to reference environment
These will be expanded to the actual environment variable value before variables. These will be expanded to the actual environment variable
sending to the server. For example, if there is an environment variable value before sending to the server. For example, if there is an
defined like FOO=bar, then a header of 'key: ${FOO}' would expand to 'key: bar'. environment variable defined like FOO=bar, then a header of
This applies to -H, -rpc-header, and -reflect-header options. No other 'key: ${FOO}' would expand to 'key: bar'. This applies to -H,
expansion/escaping is performed. This can be used to supply -rpc-header, and -reflect-header options. No other expansion/escaping is
credentials/secrets without having to put them in command-line arguments.`)) performed. This can be used to supply credentials/secrets without having
to put them in command-line arguments.`))
authority = flags.String("authority", "", prettify(` authority = flags.String("authority", "", prettify(`
Value of :authority pseudo-header to be use with underlying HTTP/2 Value of :authority pseudo-header to be use with underlying HTTP/2
requests. It defaults to the given address.`)) requests. It defaults to the given address.`))
@ -101,6 +102,13 @@ var (
will accept. If not specified, defaults to 4,194,304 (4 megabytes).`)) will accept. If not specified, defaults to 4,194,304 (4 megabytes).`))
emitDefaults = flags.Bool("emit-defaults", false, prettify(` emitDefaults = flags.Bool("emit-defaults", false, prettify(`
Emit default values for JSON-encoded responses.`)) 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.`))
msgTemplate = flags.Bool("msg-template", false, prettify(` msgTemplate = flags.Bool("msg-template", false, prettify(`
When describing messages, show a template of input data.`)) When describing messages, show a template of input data.`))
verbose = flags.Bool("v", false, prettify(` verbose = flags.Bool("v", false, prettify(`
@ -391,6 +399,9 @@ func main() {
fmt.Printf("%s\n", svc) fmt.Printf("%s\n", svc)
} }
} }
if err := writeProtoset(descSource, svcs...); err != nil {
fail(err, "Failed to write protoset to %s", *protosetOut)
}
} else { } else {
methods, err := grpcurl.ListMethods(descSource, symbol) methods, err := grpcurl.ListMethods(descSource, symbol)
if err != nil { if err != nil {
@ -403,6 +414,9 @@ func main() {
fmt.Printf("%s\n", m) fmt.Printf("%s\n", m)
} }
} }
if err := writeProtoset(descSource, symbol); err != nil {
fail(err, "Failed to write protoset to %s", *protosetOut)
}
} }
} else if describe { } else if describe {
@ -503,6 +517,9 @@ func main() {
fmt.Println(str) fmt.Println(str)
} }
} }
if err := writeProtoset(descSource, symbols...); err != nil {
fail(err, "Failed to write protoset to %s", *protosetOut)
}
} else { } else {
// Invoke an RPC // Invoke an RPC
@ -619,3 +636,15 @@ func fail(err error, msg string, args ...interface{}) {
exit(2) 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...)
}

View File

@ -3,6 +3,7 @@ package grpcurl
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"sync" "sync"
@ -251,3 +252,53 @@ func reflectionSupport(err error) error {
} }
return err return err
} }
// WriteProtoset will use the given descriptor source to resolve all of the given
// symbols and write a proto file descriptor set with their definitions to the
// given output. The output will include descriptors for all files in which the
// symbols are defined as well as their transitive dependencies.
func WriteProtoset(out io.Writer, descSource DescriptorSource, symbols ...string) error {
// compute set of file descriptors
filenames := make([]string, 0, len(symbols))
fds := make(map[string]*desc.FileDescriptor, len(symbols))
for _, sym := range symbols {
d, err := descSource.FindSymbol(sym)
if err != nil {
return fmt.Errorf("failed to find descriptor for %q: %v", sym, err)
}
fd := d.GetFile()
if _, ok := fds[fd.GetName()]; !ok {
fds[fd.GetName()] = fd
filenames = append(filenames, fd.GetName())
}
}
// now expand that to include transitive dependencies in topologically sorted
// order (such that file always appears after its dependencies)
expandedFiles := make(map[string]struct{}, len(fds))
allFilesSlice := make([]*descpb.FileDescriptorProto, 0, len(fds))
for _, filename := range filenames {
addFilesToSet(expandedFiles, &allFilesSlice, fds[filename])
}
// now we can serialize to file
b, err := proto.Marshal(&descpb.FileDescriptorSet{File: allFilesSlice})
if err != nil {
return fmt.Errorf("failed to serialize file descriptor set: %v", err)
}
if _, err := out.Write(b); err != nil {
return fmt.Errorf("failed to write file descriptor set: %v", err)
}
return nil
}
func addFilesToSet(seen map[string]struct{}, fds *[]*descpb.FileDescriptorProto, fd *desc.FileDescriptor) {
if _, ok := seen[fd.GetName()]; ok {
// already seen this one
return
}
seen[fd.GetName()] = struct{}{}
// add all dependencies first
for _, dep := range fd.GetDependencies() {
addFilesToSet(seen, fds, dep)
}
*fds = append(*fds, fd.AsFileDescriptorProto())
}