allow the use of proto source files directly, instead of having to compile to protoset files (#32)

This commit is contained in:
Joshua Humphries 2018-05-23 14:13:24 -04:00 committed by GitHub
parent e6ac789274
commit ec219b3c15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 164 additions and 57 deletions

View File

@ -52,6 +52,8 @@ var (
`File containing client private key, to present to the server. Not valid `File containing client private key, to present to the server. Not valid
with -plaintext option. Must also provide -cert option.`) with -plaintext option. Must also provide -cert option.`)
protoset multiString protoset multiString
protoFiles multiString
importPaths multiString
addlHeaders multiString addlHeaders multiString
rpcHeaders multiString rpcHeaders multiString
reflHeaders multiString reflHeaders multiString
@ -102,7 +104,26 @@ func init() {
'list' action lists the services found in the given descriptors (vs. 'list' action lists the services found in the given descriptors (vs.
those exposed by the remote server), and the 'describe' action describes those exposed by the remote server), and the 'describe' action describes
symbols found in the given descriptors. May specify more than one via symbols found in the given descriptors. May specify more than one via
multiple -protoset flags.`) multiple -protoset flags. It is an error to use both -protoset and
-proto flags.`)
flag.Var(&protoFiles, "proto",
`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.`)
flag.Var(&importPaths, "import-path",
`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.`)
} }
type multiString []string type multiString []string
@ -189,11 +210,17 @@ func main() {
if invoke && target == "" { if invoke && target == "" {
fail(nil, "No host:port specified.") fail(nil, "No host:port specified.")
} }
if len(protoset) == 0 && target == "" { if len(protoset) == 0 && len(protoFiles) == 0 && target == "" {
fail(nil, "No host:port specified and no protoset specified.") fail(nil, "No host:port specified, no protoset specified, and no proto sources specified.")
} }
if len(protoset) > 0 && len(reflHeaders) > 0 { if len(protoset) > 0 && len(reflHeaders) > 0 {
warn("The -reflect-header argument is not used when -protoset files are used ") 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.")
} }
ctx := context.Background() ctx := context.Background()
@ -260,7 +287,13 @@ func main() {
var err error var err error
descSource, err = grpcurl.DescriptorSourceFromProtoSets(protoset...) descSource, err = grpcurl.DescriptorSourceFromProtoSets(protoset...)
if err != nil { if err != nil {
fail(err, "Failed to process proto descriptor sets") fail(err, "Failed to process proto descriptor sets.")
}
} else if len(protoFiles) > 0 {
var err error
descSource, err = grpcurl.DescriptorSourceFromProtoFiles(importPaths, protoFiles...)
if err != nil {
fail(err, "Failed to process proto source files.")
} }
} else { } else {
md := grpcurl.MetadataFromHeaders(append(addlHeaders, reflHeaders...)) md := grpcurl.MetadataFromHeaders(append(addlHeaders, reflHeaders...))

View File

@ -26,6 +26,7 @@ import (
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/descriptor" "github.com/golang/protobuf/protoc-gen-go/descriptor"
"github.com/jhump/protoreflect/desc" "github.com/jhump/protoreflect/desc"
"github.com/jhump/protoreflect/desc/protoparse"
"github.com/jhump/protoreflect/dynamic" "github.com/jhump/protoreflect/dynamic"
"github.com/jhump/protoreflect/dynamic/grpcdynamic" "github.com/jhump/protoreflect/dynamic/grpcdynamic"
"github.com/jhump/protoreflect/grpcreflect" "github.com/jhump/protoreflect/grpcreflect"
@ -74,6 +75,21 @@ func DescriptorSourceFromProtoSets(fileNames ...string) (DescriptorSource, error
return DescriptorSourceFromFileDescriptorSet(files) return DescriptorSourceFromFileDescriptorSet(files)
} }
// DescriptorSourceFromProtoFiles creates a DescriptorSource that is backed by the named files,
// whose contents are Protocol Buffer source files. The given importPaths are used to locate
// any imported files.
func DescriptorSourceFromProtoFiles(importPaths []string, fileNames ...string) (DescriptorSource, error) {
p := protoparse.Parser{
ImportPaths: importPaths,
InferImportPaths: len(importPaths) == 0,
}
fds, err := p.ParseFiles(fileNames...)
if err != nil {
return nil, fmt.Errorf("could not parse given files: %v", err)
}
return DescriptorSourceFromFileDescriptors(fds...)
}
// DescriptorSourceFromFileDescriptorSet creates a DescriptorSource that is backed by the FileDescriptorSet. // DescriptorSourceFromFileDescriptorSet creates a DescriptorSource that is backed by the FileDescriptorSet.
func DescriptorSourceFromFileDescriptorSet(files *descriptor.FileDescriptorSet) (DescriptorSource, error) { func DescriptorSourceFromFileDescriptorSet(files *descriptor.FileDescriptorSet) (DescriptorSource, error) {
unresolved := map[string]*descriptor.FileDescriptorProto{} unresolved := map[string]*descriptor.FileDescriptorProto{}
@ -114,6 +130,37 @@ func resolveFileDescriptor(unresolved map[string]*descriptor.FileDescriptorProto
return result, nil return result, nil
} }
// DescriptorSourceFromFileDescriptorSet creates a DescriptorSource that is backed by the given
// file descriptors
func DescriptorSourceFromFileDescriptors(files ...*desc.FileDescriptor) (DescriptorSource, error) {
fds := map[string]*desc.FileDescriptor{}
for _, fd := range files {
if err := addFile(fd, fds); err != nil {
return nil, err
}
}
return &fileSource{files: fds}, nil
}
func addFile(fd *desc.FileDescriptor, fds map[string]*desc.FileDescriptor) error {
name := fd.GetName()
if existing, ok := fds[name]; ok {
// already added this file
if existing != fd {
// doh! duplicate files provided
return fmt.Errorf("given files include multiple copies of %q", name)
}
return nil
}
fds[name] = fd
for _, dep := range fd.GetDependencies() {
if err := addFile(dep, fds); err != nil {
return err
}
}
return nil
}
type fileSource struct { type fileSource struct {
files map[string]*desc.FileDescriptor files map[string]*desc.FileDescriptor
er *dynamic.ExtensionRegistry er *dynamic.ExtensionRegistry

View File

@ -29,18 +29,31 @@ import (
var ( var (
sourceProtoset DescriptorSource sourceProtoset DescriptorSource
ccProtoset *grpc.ClientConn sourceProtoFiles DescriptorSource
ccNoReflect *grpc.ClientConn
sourceReflect DescriptorSource sourceReflect DescriptorSource
ccReflect *grpc.ClientConn ccReflect *grpc.ClientConn
descSources []descSourceCase
) )
type descSourceCase struct {
name string
source DescriptorSource
includeRefl bool
}
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
var err error var err error
sourceProtoset, err = DescriptorSourceFromProtoSets("testing/test.protoset") sourceProtoset, err = DescriptorSourceFromProtoSets("testing/test.protoset")
if err != nil { if err != nil {
panic(err) panic(err)
} }
sourceProtoFiles, err = DescriptorSourceFromProtoFiles(nil, "../../../google.golang.org/grpc/interop/grpc_testing/test.proto")
if err != nil {
panic(err)
}
// Create a server that includes the reflection service // Create a server that includes the reflection service
svrReflect := grpc.NewServer() svrReflect := grpc.NewServer()
@ -83,17 +96,23 @@ func TestMain(m *testing.M) {
// And a corresponding client // And a corresponding client
ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
if ccProtoset, err = grpc.DialContext(ctx, fmt.Sprintf("127.0.0.1:%d", portProtoset), if ccNoReflect, err = grpc.DialContext(ctx, fmt.Sprintf("127.0.0.1:%d", portProtoset),
grpc.WithInsecure(), grpc.WithBlock()); err != nil { grpc.WithInsecure(), grpc.WithBlock()); err != nil {
panic(err) panic(err)
} }
defer ccProtoset.Close() defer ccNoReflect.Close()
descSources = []descSourceCase{
{"protoset", sourceProtoset, false},
{"proto", sourceProtoFiles, false},
{"reflect", sourceReflect, true},
}
os.Exit(m.Run()) os.Exit(m.Run())
} }
func TestServerDoesNotSupportReflection(t *testing.T) { func TestServerDoesNotSupportReflection(t *testing.T) {
refClient := grpcreflect.NewClient(context.Background(), reflectpb.NewServerReflectionClient(ccProtoset)) refClient := grpcreflect.NewClient(context.Background(), reflectpb.NewServerReflectionClient(ccNoReflect))
defer refClient.Reset() defer refClient.Reset()
refSource := DescriptorSourceFromServer(context.Background(), refClient) refSource := DescriptorSourceFromServer(context.Background(), refClient)
@ -108,7 +127,7 @@ func TestServerDoesNotSupportReflection(t *testing.T) {
t.Errorf("ListMethods should have returned ErrReflectionNotSupported; instead got %v", err) t.Errorf("ListMethods should have returned ErrReflectionNotSupported; instead got %v", err)
} }
err = InvokeRpc(context.Background(), refSource, ccProtoset, "FooService/Method", nil, nil, nil) err = InvokeRpc(context.Background(), refSource, ccNoReflect, "FooService/Method", nil, nil, nil)
// InvokeRpc wraps the error, so we just verify the returned error includes the right message // InvokeRpc wraps the error, so we just verify the returned error includes the right message
if err == nil || !strings.Contains(err.Error(), ErrReflectionNotSupported.Error()) { if err == nil || !strings.Contains(err.Error(), ErrReflectionNotSupported.Error()) {
t.Errorf("InvokeRpc should have returned ErrReflectionNotSupported; instead got %v", err) t.Errorf("InvokeRpc should have returned ErrReflectionNotSupported; instead got %v", err)
@ -137,12 +156,12 @@ func TestProtosetWithImports(t *testing.T) {
} }
} }
func TestListServicesProtoset(t *testing.T) { func TestListServices(t *testing.T) {
doTestListServices(t, sourceProtoset, false) for _, ds := range descSources {
} t.Run(ds.name, func(t *testing.T) {
doTestListServices(t, ds.source, ds.includeRefl)
func TestListServicesReflect(t *testing.T) { })
doTestListServices(t, sourceReflect, true) }
} }
func doTestListServices(t *testing.T, source DescriptorSource, includeReflection bool) { func doTestListServices(t *testing.T, source DescriptorSource, includeReflection bool) {
@ -164,12 +183,12 @@ func doTestListServices(t *testing.T, source DescriptorSource, includeReflection
} }
} }
func TestListMethodsProtoset(t *testing.T) { func TestListMethods(t *testing.T) {
doTestListMethods(t, sourceProtoset, false) for _, ds := range descSources {
} t.Run(ds.name, func(t *testing.T) {
doTestListMethods(t, ds.source, ds.includeRefl)
func TestListMethodsReflect(t *testing.T) { })
doTestListMethods(t, sourceReflect, true) }
} }
func doTestListMethods(t *testing.T, source DescriptorSource, includeReflection bool) { func doTestListMethods(t *testing.T, source DescriptorSource, includeReflection bool) {
@ -216,12 +235,12 @@ func doTestListMethods(t *testing.T, source DescriptorSource, includeReflection
} }
} }
func TestDescribeProtoset(t *testing.T) { func TestDescribe(t *testing.T) {
doTestDescribe(t, sourceProtoset) for _, ds := range descSources {
} t.Run(ds.name, func(t *testing.T) {
doTestDescribe(t, ds.source)
func TestDescribeReflect(t *testing.T) { })
doTestDescribe(t, sourceReflect) }
} }
func doTestDescribe(t *testing.T, source DescriptorSource) { func doTestDescribe(t *testing.T, source DescriptorSource) {
@ -296,12 +315,20 @@ const (
}` }`
) )
func TestUnaryProtoset(t *testing.T) { func getCC(includeRefl bool) *grpc.ClientConn {
doTestUnary(t, ccProtoset, sourceProtoset) if includeRefl {
return ccReflect
} else {
return ccNoReflect
}
} }
func TestUnaryReflect(t *testing.T) { func TestUnary(t *testing.T) {
doTestUnary(t, ccReflect, sourceReflect) for _, ds := range descSources {
t.Run(ds.name, func(t *testing.T) {
doTestUnary(t, getCC(ds.includeRefl), ds.source)
})
}
} }
func doTestUnary(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) { func doTestUnary(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) {
@ -328,12 +355,12 @@ func doTestUnary(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) {
h.check(t, "grpc.testing.TestService.UnaryCall", codes.NotFound, 1, 0) h.check(t, "grpc.testing.TestService.UnaryCall", codes.NotFound, 1, 0)
} }
func TestClientStreamProtoset(t *testing.T) { func TestClientStream(t *testing.T) {
doTestClientStream(t, ccProtoset, sourceProtoset) for _, ds := range descSources {
} t.Run(ds.name, func(t *testing.T) {
doTestClientStream(t, getCC(ds.includeRefl), ds.source)
func TestClientStreamReflect(t *testing.T) { })
doTestClientStream(t, ccReflect, sourceReflect) }
} }
func doTestClientStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) { func doTestClientStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) {
@ -373,12 +400,12 @@ func doTestClientStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSour
h.check(t, "grpc.testing.TestService.StreamingInputCall", codes.Internal, 3, 0) h.check(t, "grpc.testing.TestService.StreamingInputCall", codes.Internal, 3, 0)
} }
func TestServerStreamProtoset(t *testing.T) { func TestServerStream(t *testing.T) {
doTestServerStream(t, ccProtoset, sourceProtoset) for _, ds := range descSources {
} t.Run(ds.name, func(t *testing.T) {
doTestServerStream(t, getCC(ds.includeRefl), ds.source)
func TestServerStreamReflect(t *testing.T) { })
doTestServerStream(t, ccReflect, sourceReflect) }
} }
func doTestServerStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) { func doTestServerStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) {
@ -435,12 +462,12 @@ func doTestServerStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSour
h.check(t, "grpc.testing.TestService.StreamingOutputCall", codes.AlreadyExists, 1, 5) h.check(t, "grpc.testing.TestService.StreamingOutputCall", codes.AlreadyExists, 1, 5)
} }
func TestHalfDuplexStreamProtoset(t *testing.T) { func TestHalfDuplexStream(t *testing.T) {
doTestHalfDuplexStream(t, ccProtoset, sourceProtoset) for _, ds := range descSources {
} t.Run(ds.name, func(t *testing.T) {
doTestHalfDuplexStream(t, getCC(ds.includeRefl), ds.source)
func TestHalfDuplexStreamReflect(t *testing.T) { })
doTestHalfDuplexStream(t, ccReflect, sourceReflect) }
} }
func doTestHalfDuplexStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) { func doTestHalfDuplexStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) {
@ -480,12 +507,12 @@ func doTestHalfDuplexStream(t *testing.T, cc *grpc.ClientConn, source Descriptor
h.check(t, "grpc.testing.TestService.HalfDuplexCall", codes.DataLoss, 3, 3) h.check(t, "grpc.testing.TestService.HalfDuplexCall", codes.DataLoss, 3, 3)
} }
func TestFullDuplexStreamProtoset(t *testing.T) { func TestFullDuplexStream(t *testing.T) {
doTestFullDuplexStream(t, ccProtoset, sourceProtoset) for _, ds := range descSources {
} t.Run(ds.name, func(t *testing.T) {
doTestFullDuplexStream(t, getCC(ds.includeRefl), ds.source)
func TestFullDuplexStreamReflect(t *testing.T) { })
doTestFullDuplexStream(t, ccReflect, sourceReflect) }
} }
func doTestFullDuplexStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) { func doTestFullDuplexStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) {