allow the use of proto source files directly, instead of having to compile to protoset files (#32)
This commit is contained in:
parent
e6ac789274
commit
ec219b3c15
|
|
@ -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...))
|
||||||
|
|
|
||||||
47
grpcurl.go
47
grpcurl.go
|
|
@ -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
|
||||||
|
|
|
||||||
115
grpcurl_test.go
115
grpcurl_test.go
|
|
@ -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) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue