7 Commits

Author SHA1 Message Date
Richard Belleville
96cfd48e32 Activate xDS support in grpcurl (#137) 2020-03-13 16:29:41 -04:00
Richard Belleville
d30f3a01b7 Bump grpc to 1.28 (#136) 2020-03-12 18:55:09 -04:00
Joshua Humphries
0d669e78d0 use wrapped TransportCredentials instead of handling handshake in Dialer (#130)
* use wrapped TransportCredentials instead of handling handshake in Dialer, so that grpc library will use correct :scheme
* support -authority for TLS conns; now effectively supercedes -servername flag
2020-01-27 12:15:47 -05:00
Blake Williams
9572bd4525 Register gzip decoder (#124) 2019-11-26 08:55:50 -05:00
Joshua Humphries
ccc9007156 add -protoset-out option (#120) 2019-09-30 09:50:17 -04:00
J M
9248ea0963 Add Expand Headers Feature (#117) 2019-09-26 17:26:38 -04:00
Joshua Humphries
4054d1d115 fix go.mod for Go 1.13 (#110) 2019-08-09 14:17:53 -04:00
8 changed files with 347 additions and 43 deletions

View File

@@ -3,17 +3,17 @@ sudo: false
matrix:
include:
- go: "1.9"
- go: "1.10"
- go: "1.11"
- go: 1.9.x
- go: 1.10.x
- go: 1.11.x
env:
- GO111MODULE=off
- VET=1
- go: "1.11"
- go: 1.11.x
env: GO111MODULE=on
- go: "1.12"
- go: 1.12.x
env: GO111MODULE=off
- go: "1.12"
- go: 1.12.x
env: GO111MODULE=on
- go: tip

View File

@@ -23,6 +23,9 @@ import (
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
reflectpb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
// Register gzip compressor so compressed responses will work:
_ "google.golang.org/grpc/encoding/gzip"
)
var version = "dev build <no version set>"
@@ -52,15 +55,27 @@ var (
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.`))
protoset multiString
protoFiles multiString
importPaths multiString
addlHeaders multiString
rpcHeaders multiString
reflHeaders multiString
authority = flags.String("authority", "", prettify(`
Value of :authority pseudo-header to be use with underlying HTTP/2
requests. It defaults to the given address.`))
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.`))
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
@@ -93,12 +108,24 @@ var (
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.`))
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.`))
serverName = flags.String("servername", "", prettify(`
Override server name when validating TLS certificate.`))
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).`))
)
func init() {
@@ -286,9 +313,6 @@ func main() {
if *maxMsgSz > 0 {
opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(*maxMsgSz)))
}
if *authority != "" {
opts = append(opts, grpc.WithAuthority(*authority))
}
var creds credentials.TransportCredentials
if !*plaintext {
var err error
@@ -296,11 +320,27 @@ func main() {
if err != nil {
fail(err, "Failed to configure transport credentials")
}
if *serverName != "" {
if err := creds.OverrideServerName(*serverName); err != nil {
fail(err, "Failed to override server name as %q", *serverName)
// 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 != "" {
if err := creds.OverrideServerName(overrideName); err != nil {
fail(err, "Failed to override server name as %q", overrideName)
}
}
} else if *authority != "" {
opts = append(opts, grpc.WithAuthority(*authority))
}
network := "tcp"
if isUnixSocket != nil && isUnixSocket() {
@@ -313,6 +353,22 @@ func main() {
return cc
}
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
@@ -367,6 +423,9 @@ func main() {
fmt.Printf("%s\n", svc)
}
}
if err := writeProtoset(descSource, svcs...); err != nil {
fail(err, "Failed to write protoset to %s", *protosetOut)
}
} else {
methods, err := grpcurl.ListMethods(descSource, symbol)
if err != nil {
@@ -379,6 +438,9 @@ func main() {
fmt.Printf("%s\n", m)
}
}
if err := writeProtoset(descSource, symbol); err != nil {
fail(err, "Failed to write protoset to %s", *protosetOut)
}
}
} else if describe {
@@ -479,6 +541,9 @@ func main() {
fmt.Println(str)
}
}
if err := writeProtoset(descSource, symbols...); err != nil {
fail(err, "Failed to write protoset to %s", *protosetOut)
}
} else {
// Invoke an RPC
@@ -595,3 +660,15 @@ func fail(err error, msg string, args ...interface{}) {
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 (
"errors"
"fmt"
"io"
"io/ioutil"
"sync"
@@ -251,3 +252,53 @@ func reflectionSupport(err error) error {
}
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 {
allFilesSlice = addFilesToSet(allFilesSlice, expandedFiles, 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(allFiles []*descpb.FileDescriptorProto, expanded map[string]struct{}, fd *desc.FileDescriptor) []*descpb.FileDescriptorProto {
if _, ok := expanded[fd.GetName()]; ok {
// already seen this one
return allFiles
}
expanded[fd.GetName()] = struct{}{}
// add all dependencies first
for _, dep := range fd.GetDependencies() {
allFiles = addFilesToSet(allFiles, expanded, dep)
}
return append(allFiles, fd.AsFileDescriptorProto())
}

62
desc_source_test.go Normal file
View File

@@ -0,0 +1,62 @@
package grpcurl
import (
"bytes"
"io/ioutil"
"testing"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
)
func TestWriteProtoset(t *testing.T) {
exampleProtoset, err := loadProtoset("./testing/example.protoset")
if err != nil {
t.Fatalf("failed to load example.protoset: %v", err)
}
testProtoset, err := loadProtoset("./testing/test.protoset")
if err != nil {
t.Fatalf("failed to load test.protoset: %v", err)
}
mergedProtoset := &descriptor.FileDescriptorSet{
File: append(exampleProtoset.File, testProtoset.File...),
}
descSrc, err := DescriptorSourceFromFileDescriptorSet(mergedProtoset)
if err != nil {
t.Fatalf("failed to create descriptor source: %v", err)
}
checkWriteProtoset(t, descSrc, exampleProtoset, "TestService")
checkWriteProtoset(t, descSrc, testProtoset, "grpc.testing.TestService")
checkWriteProtoset(t, descSrc, mergedProtoset, "TestService", "grpc.testing.TestService")
}
func loadProtoset(path string) (*descriptor.FileDescriptorSet, error) {
b, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
var protoset descriptor.FileDescriptorSet
if err := proto.Unmarshal(b, &protoset); err != nil {
return nil, err
}
return &protoset, nil
}
func checkWriteProtoset(t *testing.T, descSrc DescriptorSource, protoset *descriptor.FileDescriptorSet, symbols ...string) {
var buf bytes.Buffer
if err := WriteProtoset(&buf, descSrc, symbols...); err != nil {
t.Fatalf("failed to write protoset: %v", err)
}
var result descriptor.FileDescriptorSet
if err := proto.Unmarshal(buf.Bytes(), &result); err != nil {
t.Fatalf("failed to unmarshal written protoset: %v", err)
}
if !proto.Equal(protoset, &result) {
t.Fatalf("written protoset not equal to input:\nExpecting: %s\nActual: %s", protoset, &result)
}
}

6
go.mod
View File

@@ -1,8 +1,8 @@
module github.com/fullstorydev/grpcurl
require (
github.com/golang/protobuf v1.3.1
github.com/jhump/protoreflect v1.4.4
github.com/golang/protobuf v1.3.5
github.com/jhump/protoreflect v1.5.0
golang.org/x/net v0.0.0-20190311183353-d8887717615a
google.golang.org/grpc v1.21.0
google.golang.org/grpc v1.28.0
)

42
go.sum
View File

@@ -1,30 +1,58 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/jhump/protoreflect v1.4.4 h1:kySdALZUh7xRtW6UoZjjHtlR8k7rLzx5EXJFRvsO5UY=
github.com/jhump/protoreflect v1.4.4/go.mod h1:gZ3i/BeD62fjlaIL0VW4UDMT70CTX+3m4pOnAlJ0BX8=
github.com/jhump/protoreflect v1.5.0 h1:NgpVT+dX71c8hZnxHof2M7QDK7QtohIJ7DYycjnkyfc=
github.com/jhump/protoreflect v1.5.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20170818100345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -15,6 +15,8 @@ import (
"fmt"
"io/ioutil"
"net"
"os"
"regexp"
"sort"
"strings"
@@ -30,6 +32,7 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
_ "google.golang.org/grpc/xds/experimental"
)
// ListServices uses the given descriptor source to return a sorted list of fully-qualified
@@ -161,6 +164,36 @@ func MetadataFromHeaders(headers []string) metadata.MD {
return md
}
var envVarRegex = regexp.MustCompile(`\${\w+}`)
// ExpandHeaders expands environment variables contained in the header string.
// If no corresponding environment variable is found an error is returned.
// TODO: Add escaping for `${`
func ExpandHeaders(headers []string) ([]string, error) {
expandedHeaders := make([]string, len(headers))
for idx, header := range headers {
if header == "" {
continue
}
results := envVarRegex.FindAllString(header, -1)
if len(results) == 0 {
expandedHeaders[idx] = headers[idx]
continue
}
expandedHeader := header
for _, result := range results {
envVarName := result[2 : len(result)-1] // strip leading `${` and trailing `}`
envVarValue, ok := os.LookupEnv(envVarName)
if !ok {
return nil, fmt.Errorf("header %q refers to missing environment variable %q", header, envVarName)
}
expandedHeader = strings.Replace(expandedHeader, result, envVarValue, -1)
}
expandedHeaders[idx] = expandedHeader
}
return expandedHeaders, nil
}
var base64Codecs = []*base64.Encoding{base64.StdEncoding, base64.URLEncoding, base64.RawStdEncoding, base64.RawURLEncoding}
func decode(val string) (string, error) {
@@ -573,33 +606,44 @@ func BlockingDial(ctx context.Context, network, address string, creds credential
}
}
// custom credentials and dialer will notify on error via the
// writeResult function
if creds != nil {
creds = &errSignalingCreds{
TransportCredentials: creds,
writeResult: writeResult,
}
}
dialer := func(ctx context.Context, address string) (net.Conn, error) {
// NB: We *could* handle the TLS handshake ourselves, in the custom
// dialer (instead of customizing both the dialer and the credentials).
// But that requires using WithInsecure dial option (so that the gRPC
// library doesn't *also* try to do a handshake). And that would mean
// that the library would send the wrong ":scheme" metaheader to
// servers: it would send "http" instead of "https" because it is
// unaware that TLS is actually in use.
conn, err := (&net.Dialer{}).DialContext(ctx, network, address)
if err != nil {
writeResult(err)
return nil, err
}
if creds != nil {
conn, _, err = creds.ClientHandshake(ctx, address, conn)
if err != nil {
writeResult(err)
return nil, err
}
}
return conn, nil
return conn, err
}
// Even with grpc.FailOnNonTempDialError, this call will usually timeout in
// the face of TLS handshake errors. So we can't rely on grpc.WithBlock() to
// know when we're done. So we run it in a goroutine and then use result
// channel to either get the channel or fail-fast.
// channel to either get the connection or fail-fast.
go func() {
opts = append(opts,
grpc.WithBlock(),
grpc.FailOnNonTempDialError(true),
grpc.WithContextDialer(dialer),
grpc.WithInsecure(), // we are handling TLS, so tell grpc not to
)
if creds == nil {
opts = append(opts, grpc.WithInsecure())
} else {
opts = append(opts, grpc.WithTransportCredentials(creds))
}
conn, err := grpc.DialContext(ctx, address, opts...)
var res interface{}
if err != nil {
@@ -620,3 +664,18 @@ func BlockingDial(ctx context.Context, network, address string, creds credential
return nil, ctx.Err()
}
}
// errSignalingCreds is a wrapper around a TransportCredentials value, but
// it will use the writeResult function to notify on error.
type errSignalingCreds struct {
credentials.TransportCredentials
writeResult func(res interface{})
}
func (c *errSignalingCreds) ClientHandshake(ctx context.Context, addr string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
conn, auth, err := c.TransportCredentials.ClientHandshake(ctx, addr, rawConn)
if err != nil {
c.writeResult(err)
}
return conn, auth, err
}

View File

@@ -300,6 +300,33 @@ func TestGetAllFiles(t *testing.T) {
}
}
func TestExpandHeaders(t *testing.T) {
inHeaders := []string{"key1: ${value}", "key2: bar", "key3: ${woo", "key4: woo}", "key5: ${TEST}",
"key6: ${TEST_VAR}", "${TEST}: ${TEST_VAR}", "key8: ${EMPTY}"}
os.Setenv("value", "value")
os.Setenv("TEST", "value5")
os.Setenv("TEST_VAR", "value6")
os.Setenv("EMPTY", "")
expectedHeaders := map[string]bool{"key1: value": true, "key2: bar": true, "key3: ${woo": true, "key4: woo}": true,
"key5: value5": true, "key6: value6": true, "value5: value6": true, "key8: ": true}
outHeaders, err := ExpandHeaders(inHeaders)
if err != nil {
t.Errorf("The ExpandHeaders function generated an unexpected error %s", err)
}
for _, expandedHeader := range outHeaders {
if _, ok := expectedHeaders[expandedHeader]; !ok {
t.Errorf("The ExpandHeaders function has returned an unexpected header. Received unexpected header %s", expandedHeader)
}
}
badHeaders := []string{"key: ${DNE}"}
_, err = ExpandHeaders(badHeaders)
if err == nil {
t.Errorf("The ExpandHeaders function should return an error for missing environment variables %q", badHeaders)
}
}
func fileNames(files []*desc.FileDescriptor) []string {
names := make([]string, len(files))
for i, f := range files {