Compare commits
55 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
c54eac28fd | |
|
|
6caf0e77fa | |
|
|
1ad1dc15dd | |
|
|
f575e91b2c | |
|
|
ed672b2bc9 | |
|
|
eab6c910a6 | |
|
|
60e53f304f | |
|
|
f093930c85 | |
|
|
7ad93a42d9 | |
|
|
7155fb6211 | |
|
|
f28d506cea | |
|
|
58ccc6321e | |
|
|
b519ffc959 | |
|
|
3a8fa31879 | |
|
|
614b1687cf | |
|
|
30f87c1323 | |
|
|
78655b4786 | |
|
|
d00c28104b | |
|
|
9e3e083f29 | |
|
|
c32936d71e | |
|
|
f3c8ec2564 | |
|
|
7e1a6c9068 | |
|
|
b9a11e9fea | |
|
|
bc5cf811a0 | |
|
|
fb49f049e6 | |
|
|
cdb43b08fa | |
|
|
56181ba330 | |
|
|
46c38b351a | |
|
|
a05d48d6dd | |
|
|
fc63514da1 | |
|
|
e14d9f769a | |
|
|
239dde4a62 | |
|
|
80e833a557 | |
|
|
6fccd7757e | |
|
|
400fa5f2d3 | |
|
|
0e13e85e65 | |
|
|
07361b21ea | |
|
|
8e76884d21 | |
|
|
805ce40c63 | |
|
|
93ea011b36 | |
|
|
5592211a41 | |
|
|
184c8f70b5 | |
|
|
149a93e0ec | |
|
|
252b57fd45 | |
|
|
24b80dfed8 | |
|
|
334e3f56de | |
|
|
f4157743ed | |
|
|
79fb35f680 | |
|
|
7ccaf0a21f | |
|
|
6093b09afa | |
|
|
70c215f7e2 | |
|
|
28c0ee28f0 | |
|
|
bc2944de97 | |
|
|
7a845ca5e9 | |
|
|
c17f0782f7 |
|
|
@ -9,22 +9,22 @@ shared_configs:
|
||||||
# Use the latest 2.1 version of CircleCI pipeline process engine. See: https://circleci.com/docs/2.0/configuration-reference
|
# Use the latest 2.1 version of CircleCI pipeline process engine. See: https://circleci.com/docs/2.0/configuration-reference
|
||||||
version: 2.1
|
version: 2.1
|
||||||
jobs:
|
jobs:
|
||||||
build-1-18:
|
build-1-23:
|
||||||
working_directory: ~/repo
|
working_directory: ~/repo
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/go:1.18
|
- image: cimg/go:1.23
|
||||||
steps: *simple_job_steps
|
steps: *simple_job_steps
|
||||||
|
|
||||||
build-1-19:
|
build-1-24:
|
||||||
working_directory: ~/repo
|
working_directory: ~/repo
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/go:1.19
|
- image: cimg/go:1.24
|
||||||
steps: *simple_job_steps
|
steps: *simple_job_steps
|
||||||
|
|
||||||
build-1-20:
|
build-1-25:
|
||||||
working_directory: ~/repo
|
working_directory: ~/repo
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/go:1.20
|
- image: cimg/go:1.25
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
|
|
@ -32,16 +32,9 @@ jobs:
|
||||||
command: |
|
command: |
|
||||||
make ci
|
make ci
|
||||||
|
|
||||||
build-1-21:
|
|
||||||
working_directory: ~/repo
|
|
||||||
docker:
|
|
||||||
- image: cimg/go:1.21
|
|
||||||
steps: *simple_job_steps
|
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
pr-build-test:
|
pr-build-test:
|
||||||
jobs:
|
jobs:
|
||||||
- build-1-18
|
- build-1-23
|
||||||
- build-1-19
|
- build-1-24
|
||||||
- build-1-20
|
- build-1-25
|
||||||
- build-1-21
|
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,4 @@ dist/
|
||||||
.idea/
|
.idea/
|
||||||
VERSION
|
VERSION
|
||||||
.tmp/
|
.tmp/
|
||||||
|
*.snap
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,23 @@ builds:
|
||||||
goarch:
|
goarch:
|
||||||
- amd64
|
- amd64
|
||||||
- 386
|
- 386
|
||||||
|
- arm
|
||||||
- arm64
|
- arm64
|
||||||
- s390x
|
- s390x
|
||||||
- ppc64le
|
- ppc64le
|
||||||
|
goarm:
|
||||||
|
- 5
|
||||||
|
- 6
|
||||||
|
- 7
|
||||||
ignore:
|
ignore:
|
||||||
- goos: darwin
|
- goos: darwin
|
||||||
goarch: 386
|
goarch: 386
|
||||||
- goos: windows
|
- goos: windows
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
|
- goos: darwin
|
||||||
|
goarch: arm
|
||||||
|
- goos: windows
|
||||||
|
goarch: arm
|
||||||
- goos: darwin
|
- goos: darwin
|
||||||
goarch: s390x
|
goarch: s390x
|
||||||
- goos: windows
|
- goos: windows
|
||||||
|
|
@ -29,12 +38,26 @@ builds:
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- format: tar.gz
|
- format: tar.gz
|
||||||
|
name_template: >-
|
||||||
|
{{ .Binary }}_{{ .Version }}_
|
||||||
|
{{- if eq .Os "darwin" }}osx{{ else }}{{ .Os }}{{ end }}_
|
||||||
|
{{- if eq .Arch "amd64" }}x86_64
|
||||||
|
{{- else if eq .Arch "386" }}x86_32
|
||||||
|
{{- else }}{{ .Arch }}{{ end }}
|
||||||
|
{{- with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}
|
||||||
format_overrides:
|
format_overrides:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
format: zip
|
format: zip
|
||||||
replacements:
|
|
||||||
amd64: x86_64
|
|
||||||
386: x86_32
|
|
||||||
darwin: osx
|
|
||||||
files:
|
files:
|
||||||
- LICENSE
|
- LICENSE
|
||||||
|
|
||||||
|
nfpms:
|
||||||
|
- vendor: Fullstory
|
||||||
|
homepage: https://github.com/fullstorydev/grpcurl/
|
||||||
|
maintainer: Engineering at Fullstory <fixme@fixme>
|
||||||
|
description: 'Like cURL, but for gRPC: Command-line tool for interacting with gRPC servers'
|
||||||
|
license: MIT
|
||||||
|
id: nfpms
|
||||||
|
formats:
|
||||||
|
- deb
|
||||||
|
- rpm
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
FROM golang:1.18-alpine as builder
|
FROM golang:1.25-alpine AS builder
|
||||||
MAINTAINER FullStory Engineering
|
LABEL maintainer="Fullstory Engineering"
|
||||||
|
|
||||||
# create non-privileged group and user
|
# create non-privileged group and user
|
||||||
RUN addgroup -S grpcurl && adduser -S grpcurl -G grpcurl
|
RUN addgroup -S grpcurl && adduser -S grpcurl -G grpcurl
|
||||||
|
|
@ -16,7 +16,7 @@ RUN go build -o /grpcurl \
|
||||||
-ldflags "-w -extldflags \"-static\" -X \"main.version=$(cat VERSION)\"" \
|
-ldflags "-w -extldflags \"-static\" -X \"main.version=$(cat VERSION)\"" \
|
||||||
./cmd/grpcurl
|
./cmd/grpcurl
|
||||||
|
|
||||||
FROM alpine:3 as alpine
|
FROM alpine:3 AS alpine
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
COPY --from=builder /etc/passwd /etc/passwd
|
COPY --from=builder /etc/passwd /etc/passwd
|
||||||
|
|
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2017 FullStory, Inc
|
Copyright (c) 2017 Fullstory, Inc
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
||||||
29
Makefile
29
Makefile
|
|
@ -1,8 +1,11 @@
|
||||||
dev_build_version=$(shell git describe --tags --always --dirty)
|
dev_build_version=$(shell git describe --tags --always --dirty)
|
||||||
|
|
||||||
export PATH := $(shell pwd)/.tmp/protoc/bin:$(PATH)
|
export PATH := $(shell pwd)/.tmp/protoc/bin:$(PATH)
|
||||||
|
|
||||||
export PROTOC_VERSION := 22.0
|
export PROTOC_VERSION := 22.0
|
||||||
|
# Disable CGO for improved compatibility across distros
|
||||||
|
export CGO_ENABLED=0
|
||||||
|
export GOFLAGS=-trimpath
|
||||||
|
export GOWORK=off
|
||||||
|
|
||||||
# TODO: run golint and errcheck, but only to catch *new* violations and
|
# TODO: run golint and errcheck, but only to catch *new* violations and
|
||||||
# decide whether to change code or not (e.g. we need to be able to whitelist
|
# decide whether to change code or not (e.g. we need to be able to whitelist
|
||||||
|
|
@ -15,10 +18,12 @@ ci: deps checkgofmt checkgenerate vet staticcheck ineffassign predeclared test
|
||||||
.PHONY: deps
|
.PHONY: deps
|
||||||
deps:
|
deps:
|
||||||
go get -d -v -t ./...
|
go get -d -v -t ./...
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
.PHONY: updatedeps
|
.PHONY: updatedeps
|
||||||
updatedeps:
|
updatedeps:
|
||||||
go get -d -v -t -u -f ./...
|
go get -d -v -t -u -f ./...
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
install:
|
install:
|
||||||
|
|
@ -26,8 +31,8 @@ install:
|
||||||
|
|
||||||
.PHONY: release
|
.PHONY: release
|
||||||
release:
|
release:
|
||||||
@go install github.com/goreleaser/goreleaser@v1.10.0
|
@go install github.com/goreleaser/goreleaser@v1.21.0
|
||||||
goreleaser release --rm-dist
|
goreleaser release --clean
|
||||||
|
|
||||||
.PHONY: docker
|
.PHONY: docker
|
||||||
docker:
|
docker:
|
||||||
|
|
@ -39,13 +44,15 @@ docker:
|
||||||
generate: .tmp/protoc/bin/protoc
|
generate: .tmp/protoc/bin/protoc
|
||||||
@go install google.golang.org/protobuf/cmd/protoc-gen-go@a709e31e5d12
|
@go install google.golang.org/protobuf/cmd/protoc-gen-go@a709e31e5d12
|
||||||
@go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0
|
@go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0
|
||||||
|
@go install github.com/jhump/protoreflect/desc/sourceinfo/cmd/protoc-gen-gosrcinfo@v1.14.1
|
||||||
go generate ./...
|
go generate ./...
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
.PHONY: checkgenerate
|
.PHONY: checkgenerate
|
||||||
checkgenerate: generate
|
checkgenerate: generate
|
||||||
git status --porcelain
|
git status --porcelain -- '**/*.go'
|
||||||
@if [ -n "$$(git status --porcelain)" ]; then \
|
@if [ -n "$$(git status --porcelain -- '**/*.go')" ]; then \
|
||||||
git diff; \
|
git diff -- '**/*.go'; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -62,8 +69,8 @@ vet:
|
||||||
|
|
||||||
.PHONY: staticcheck
|
.PHONY: staticcheck
|
||||||
staticcheck:
|
staticcheck:
|
||||||
@go install honnef.co/go/tools/cmd/staticcheck@v0.4.3
|
@go install honnef.co/go/tools/cmd/staticcheck@2025.1.1
|
||||||
staticcheck ./...
|
staticcheck -checks "inherit,-SA1019" ./...
|
||||||
|
|
||||||
.PHONY: ineffassign
|
.PHONY: ineffassign
|
||||||
ineffassign:
|
ineffassign:
|
||||||
|
|
@ -72,7 +79,7 @@ ineffassign:
|
||||||
|
|
||||||
.PHONY: predeclared
|
.PHONY: predeclared
|
||||||
predeclared:
|
predeclared:
|
||||||
@go install github.com/nishanths/predeclared@5f2f810c9ae6
|
@go install github.com/nishanths/predeclared@51e8c974458a0f93dc03fe356f91ae1a6d791e6f
|
||||||
predeclared ./...
|
predeclared ./...
|
||||||
|
|
||||||
# Intentionally omitted from CI, but target here for ad-hoc reports.
|
# Intentionally omitted from CI, but target here for ad-hoc reports.
|
||||||
|
|
@ -88,8 +95,8 @@ errcheck:
|
||||||
errcheck ./...
|
errcheck ./...
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test: deps
|
||||||
go test -race ./...
|
CGO_ENABLED=1 go test -race ./...
|
||||||
|
|
||||||
.tmp/protoc/bin/protoc: ./Makefile ./download_protoc.sh
|
.tmp/protoc/bin/protoc: ./Makefile ./download_protoc.sh
|
||||||
./download_protoc.sh
|
./download_protoc.sh
|
||||||
|
|
|
||||||
21
README.md
21
README.md
|
|
@ -1,6 +1,7 @@
|
||||||
# gRPCurl
|
# gRPCurl
|
||||||
[](https://circleci.com/gh/fullstorydev/grpcurl/tree/master)
|
[](https://circleci.com/gh/fullstorydev/grpcurl/tree/master)
|
||||||
[](https://goreportcard.com/report/github.com/fullstorydev/grpcurl)
|
[](https://goreportcard.com/report/github.com/fullstorydev/grpcurl)
|
||||||
|
[](https://snapcraft.io/grpcurl)
|
||||||
|
|
||||||
`grpcurl` is a command-line tool that lets you interact with gRPC servers. It's
|
`grpcurl` is a command-line tool that lets you interact with gRPC servers. It's
|
||||||
basically `curl` for gRPC servers.
|
basically `curl` for gRPC servers.
|
||||||
|
|
@ -79,6 +80,12 @@ of environments, including Windows and myriad Linux distributions.
|
||||||
You can see more details and the full list of other packages for `grpcurl` at _repology.org_:
|
You can see more details and the full list of other packages for `grpcurl` at _repology.org_:
|
||||||
https://repology.org/project/grpcurl/information
|
https://repology.org/project/grpcurl/information
|
||||||
|
|
||||||
|
### Snap
|
||||||
|
|
||||||
|
You can install `grpcurl` using the snap package:
|
||||||
|
|
||||||
|
`snap install grpcurl`
|
||||||
|
|
||||||
### From Source
|
### From Source
|
||||||
If you already have the [Go SDK](https://golang.org/doc/install) installed, you can use the `go`
|
If you already have the [Go SDK](https://golang.org/doc/install) installed, you can use the `go`
|
||||||
tool to install `grpcurl`:
|
tool to install `grpcurl`:
|
||||||
|
|
@ -145,6 +152,13 @@ grpcurl -d @ grpc.server.com:443 my.custom.server.Service/Method <<EOM
|
||||||
}
|
}
|
||||||
EOM
|
EOM
|
||||||
```
|
```
|
||||||
|
### Adding Headers/Metadata to Request
|
||||||
|
Adding of headers / metadata to a rpc request is possible via the `-H name:value` command line option. Multiple headers can be added in a similar fashion.
|
||||||
|
Example :
|
||||||
|
```shell
|
||||||
|
grpcurl -H header1:value1 -H header2:value2 -d '{"id": 1234, "tags": ["foo","bar"]}' grpc.server.com:443 my.custom.server.Service/Method
|
||||||
|
```
|
||||||
|
For more usage guide, check out the help docs via `grpcurl -help`
|
||||||
|
|
||||||
### Listing Services
|
### Listing Services
|
||||||
To list all services exposed by a server, use the "list" verb. When using `.proto` source
|
To list all services exposed by a server, use the "list" verb. When using `.proto` source
|
||||||
|
|
@ -159,6 +173,13 @@ grpcurl -protoset my-protos.bin list
|
||||||
|
|
||||||
# Using proto sources
|
# Using proto sources
|
||||||
grpcurl -import-path ../protos -proto my-stuff.proto list
|
grpcurl -import-path ../protos -proto my-stuff.proto list
|
||||||
|
|
||||||
|
# Export proto files (use -proto-out-dir to specify the output directory)
|
||||||
|
grpcurl -plaintext -proto-out-dir "out_protos" "localhost:8787" describe my.custom.server.Service
|
||||||
|
|
||||||
|
# Export protoset file (use -protoset-out to specify the output file)
|
||||||
|
grpcurl -plaintext -protoset-out "out.protoset" "localhost:8787" describe my.custom.server.Service
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The "list" verb also lets you see all methods in a particular service:
|
The "list" verb also lets you see all methods in a particular service:
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
//go:build go1.10
|
|
||||||
// +build go1.10
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
func indent() string {
|
|
||||||
// In Go 1.10 and up, the flag package automatically
|
|
||||||
// adds the right indentation.
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
//go:build !go1.10
|
|
||||||
// +build !go1.10
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
func indent() string {
|
|
||||||
// In Go 1.9 and older, we need to add indentation
|
|
||||||
// after newlines in the flag doc strings.
|
|
||||||
return " \t"
|
|
||||||
}
|
|
||||||
|
|
@ -8,17 +8,19 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jhump/protoreflect/desc"
|
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 required to use APIs in other grpcurl package
|
||||||
"github.com/jhump/protoreflect/grpcreflect"
|
"github.com/jhump/protoreflect/grpcreflect"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
|
"google.golang.org/grpc/credentials/alts"
|
||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
|
|
@ -32,9 +34,9 @@ import (
|
||||||
"github.com/fullstorydev/grpcurl"
|
"github.com/fullstorydev/grpcurl"
|
||||||
)
|
)
|
||||||
|
|
||||||
// To avoid confusion between program error codes and the gRPC resonse
|
// To avoid confusion between program error codes and the gRPC response
|
||||||
// status codes 'Cancelled' and 'Unknown', 1 and 2 respectively,
|
// status codes 'Cancelled' and 'Unknown', 1 and 2 respectively,
|
||||||
// the response status codes emitted use an offest of 64
|
// the response status codes emitted use an offset of 64
|
||||||
const statusCodeOffset = 64
|
const statusCodeOffset = 64
|
||||||
|
|
||||||
const noVersion = "dev build <no version set>"
|
const noVersion = "dev build <no version set>"
|
||||||
|
|
@ -52,11 +54,14 @@ var (
|
||||||
Print usage instructions and exit.`))
|
Print usage instructions and exit.`))
|
||||||
printVersion = flags.Bool("version", false, prettify(`
|
printVersion = flags.Bool("version", false, prettify(`
|
||||||
Print version.`))
|
Print version.`))
|
||||||
|
|
||||||
plaintext = flags.Bool("plaintext", false, prettify(`
|
plaintext = flags.Bool("plaintext", false, prettify(`
|
||||||
Use plain-text HTTP/2 when connecting to server (no TLS).`))
|
Use plain-text HTTP/2 when connecting to server (no TLS).`))
|
||||||
insecure = flags.Bool("insecure", false, prettify(`
|
insecure = flags.Bool("insecure", false, prettify(`
|
||||||
Skip server certificate and domain verification. (NOT SECURE!) Not
|
Skip server certificate and domain verification. (NOT SECURE!) Not
|
||||||
valid with -plaintext option.`))
|
valid with -plaintext option.`))
|
||||||
|
|
||||||
|
// TLS Options
|
||||||
cacert = flags.String("cacert", "", prettify(`
|
cacert = flags.String("cacert", "", prettify(`
|
||||||
File containing trusted root certificates for verifying the server.
|
File containing trusted root certificates for verifying the server.
|
||||||
Ignored if -insecure is specified.`))
|
Ignored if -insecure is specified.`))
|
||||||
|
|
@ -66,6 +71,13 @@ var (
|
||||||
key = flags.String("key", "", prettify(`
|
key = flags.String("key", "", prettify(`
|
||||||
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.`))
|
||||||
|
|
||||||
|
// 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
|
protoset multiString
|
||||||
protoFiles multiString
|
protoFiles multiString
|
||||||
importPaths multiString
|
importPaths multiString
|
||||||
|
|
@ -86,7 +98,8 @@ var (
|
||||||
value of the ":authority" pseudo-header in the HTTP/2 protocol. When TLS
|
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
|
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
|
server's certificate. It defaults to the address that is provided in the
|
||||||
positional arguments.`))
|
positional arguments, or 'localhost' in the case of a unix domain
|
||||||
|
socket.`))
|
||||||
userAgent = flags.String("user-agent", "", prettify(`
|
userAgent = flags.String("user-agent", "", prettify(`
|
||||||
If set, the specified value will be added to the User-Agent header set
|
If set, the specified value will be added to the User-Agent header set
|
||||||
by the grpc-go library.
|
by the grpc-go library.
|
||||||
|
|
@ -122,9 +135,11 @@ var (
|
||||||
is received for this same period then the connection is closed and the
|
is received for this same period then the connection is closed and the
|
||||||
operation fails.`))
|
operation fails.`))
|
||||||
maxTime = flags.Float64("max-time", 0, prettify(`
|
maxTime = flags.Float64("max-time", 0, prettify(`
|
||||||
The maximum total time the operation can take, in seconds. This is
|
The maximum total time the operation can take, in seconds. This sets a
|
||||||
useful for preventing batch jobs that use grpcurl from hanging due to
|
timeout on the gRPC context, allowing both client and server to give up
|
||||||
slow or bad network links or due to incorrect stream method usage.`))
|
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(`
|
maxMsgSz = flags.Int("max-msg-sz", 0, prettify(`
|
||||||
The maximum encoded size of a response message, in bytes, that grpcurl
|
The maximum encoded size of a response message, in bytes, that grpcurl
|
||||||
will accept. If not specified, defaults to 4,194,304 (4 megabytes).`))
|
will accept. If not specified, defaults to 4,194,304 (4 megabytes).`))
|
||||||
|
|
@ -137,12 +152,20 @@ var (
|
||||||
file if this option is given. When invoking an RPC and this option is
|
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
|
given, the method being invoked and its transitive dependencies will be
|
||||||
included in the output file.`))
|
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(`
|
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(`
|
||||||
Enable verbose output.`))
|
Enable verbose output.`))
|
||||||
veryVerbose = flags.Bool("vv", false, prettify(`
|
veryVerbose = flags.Bool("vv", false, prettify(`
|
||||||
Enable very verbose output.`))
|
Enable very verbose output (includes timing data).`))
|
||||||
serverName = flags.String("servername", "", prettify(`
|
serverName = flags.String("servername", "", prettify(`
|
||||||
Override server name when validating TLS certificate. This flag is
|
Override server name when validating TLS certificate. This flag is
|
||||||
ignored if -plaintext or -insecure is used.
|
ignored if -plaintext or -insecure is used.
|
||||||
|
|
@ -199,6 +222,14 @@ func init() {
|
||||||
-use-reflection is used in combination with a -proto or -protoset flag,
|
-use-reflection is used in combination with a -proto or -protoset flag,
|
||||||
the provided descriptor sources will be used in addition to server
|
the provided descriptor sources will be used in addition to server
|
||||||
reflection to resolve messages and extensions.`))
|
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
|
type multiString []string
|
||||||
|
|
@ -255,6 +286,32 @@ func (cs compositeSource) AllExtensionsForType(typeName string) ([]*desc.FieldDe
|
||||||
return exts, nil
|
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() {
|
func main() {
|
||||||
flags.Usage = usage
|
flags.Usage = usage
|
||||||
flags.Parse(os.Args[1:])
|
flags.Parse(os.Args[1:])
|
||||||
|
|
@ -267,6 +324,9 @@ func main() {
|
||||||
os.Exit(0)
|
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.
|
// Do extra validation on arguments and figure out what user asked us to do.
|
||||||
if *connectTimeout < 0 {
|
if *connectTimeout < 0 {
|
||||||
fail(nil, "The -connect-timeout argument must not be negative.")
|
fail(nil, "The -connect-timeout argument must not be negative.")
|
||||||
|
|
@ -280,18 +340,27 @@ func main() {
|
||||||
if *maxMsgSz < 0 {
|
if *maxMsgSz < 0 {
|
||||||
fail(nil, "The -max-msg-sz argument must not be negative.")
|
fail(nil, "The -max-msg-sz argument must not be negative.")
|
||||||
}
|
}
|
||||||
if *plaintext && *insecure {
|
if *plaintext && *usealts {
|
||||||
fail(nil, "The -plaintext and -insecure arguments are mutually exclusive.")
|
fail(nil, "The -plaintext and -alts arguments are mutually exclusive.")
|
||||||
}
|
}
|
||||||
if *plaintext && *cert != "" {
|
if *insecure && !usetls {
|
||||||
fail(nil, "The -plaintext and -cert arguments are mutually exclusive.")
|
fail(nil, "The -insecure argument can only be used with TLS.")
|
||||||
}
|
}
|
||||||
if *plaintext && *key != "" {
|
if *cert != "" && !usetls {
|
||||||
fail(nil, "The -plaintext and -key arguments are mutually exclusive.")
|
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 == "") {
|
if (*key == "") != (*cert == "") {
|
||||||
fail(nil, "The -cert and -key arguments must be used together and both be present.")
|
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" {
|
if *format != "json" && *format != "text" {
|
||||||
fail(nil, "The -format option must be 'json' or 'text'.")
|
fail(nil, "The -format option must be 'json' or 'text'.")
|
||||||
}
|
}
|
||||||
|
|
@ -328,8 +397,16 @@ func main() {
|
||||||
if *verbose {
|
if *verbose {
|
||||||
verbosityLevel = 1
|
verbosityLevel = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rootTiming *timingData
|
||||||
if *veryVerbose {
|
if *veryVerbose {
|
||||||
verbosityLevel = 2
|
verbosityLevel = 2
|
||||||
|
|
||||||
|
rootTiming = &timingData{Title: "Timing Data", Start: time.Now()}
|
||||||
|
defer func() {
|
||||||
|
rootTiming.Done()
|
||||||
|
dumpTiming(rootTiming, 0)
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
var symbol string
|
var symbol string
|
||||||
|
|
@ -381,22 +458,24 @@ func main() {
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if *maxTime > 0 {
|
if *maxTime > 0 {
|
||||||
timeout := time.Duration(*maxTime * float64(time.Second))
|
timeout := floatSecondsToDuration(*maxTime)
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
ctx, cancel = context.WithTimeout(ctx, timeout)
|
ctx, cancel = context.WithTimeout(ctx, timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
dial := func() *grpc.ClientConn {
|
dial := func() *grpc.ClientConn {
|
||||||
|
dialTiming := rootTiming.Child("Dial")
|
||||||
|
defer dialTiming.Done()
|
||||||
dialTime := 10 * time.Second
|
dialTime := 10 * time.Second
|
||||||
if *connectTimeout > 0 {
|
if *connectTimeout > 0 {
|
||||||
dialTime = time.Duration(*connectTimeout * float64(time.Second))
|
dialTime = floatSecondsToDuration(*connectTimeout)
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(ctx, dialTime)
|
ctx, cancel := context.WithTimeout(ctx, dialTime)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
var opts []grpc.DialOption
|
var opts []grpc.DialOption
|
||||||
if *keepaliveTime > 0 {
|
if *keepaliveTime > 0 {
|
||||||
timeout := time.Duration(*keepaliveTime * float64(time.Second))
|
timeout := floatSecondsToDuration(*keepaliveTime)
|
||||||
opts = append(opts, grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
opts = append(opts, grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||||
Time: timeout,
|
Time: timeout,
|
||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
|
|
@ -405,8 +484,31 @@ func main() {
|
||||||
if *maxMsgSz > 0 {
|
if *maxMsgSz > 0 {
|
||||||
opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(*maxMsgSz)))
|
opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(*maxMsgSz)))
|
||||||
}
|
}
|
||||||
|
if isUnixSocket != nil && isUnixSocket() && !strings.HasPrefix(target, "unix://") {
|
||||||
|
// prepend unix:// to the address if it's not already there
|
||||||
|
// this is to maintain backwards compatibility because the custom dialer is replaced by
|
||||||
|
// the default dialer in grpc-go.
|
||||||
|
// https://github.com/fullstorydev/grpcurl/pull/480
|
||||||
|
target = "unix://" + target
|
||||||
|
}
|
||||||
var creds credentials.TransportCredentials
|
var creds credentials.TransportCredentials
|
||||||
if !*plaintext {
|
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)
|
tlsConf, err := grpcurl.ClientTLSConfig(*insecure, *cacert, *cert, *key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fail(err, "Failed to create TLS config")
|
fail(err, "Failed to create TLS config")
|
||||||
|
|
@ -439,8 +541,9 @@ func main() {
|
||||||
if overrideName != "" {
|
if overrideName != "" {
|
||||||
opts = append(opts, grpc.WithAuthority(overrideName))
|
opts = append(opts, grpc.WithAuthority(overrideName))
|
||||||
}
|
}
|
||||||
} else if *authority != "" {
|
tlsTiming.Done()
|
||||||
opts = append(opts, grpc.WithAuthority(*authority))
|
} else {
|
||||||
|
panic("Should have defaulted to use TLS.")
|
||||||
}
|
}
|
||||||
|
|
||||||
grpcurlUA := "grpcurl/" + version
|
grpcurlUA := "grpcurl/" + version
|
||||||
|
|
@ -452,11 +555,9 @@ func main() {
|
||||||
}
|
}
|
||||||
opts = append(opts, grpc.WithUserAgent(grpcurlUA))
|
opts = append(opts, grpc.WithUserAgent(grpcurlUA))
|
||||||
|
|
||||||
network := "tcp"
|
blockingDialTiming := dialTiming.Child("BlockingDial")
|
||||||
if isUnixSocket != nil && isUnixSocket() {
|
defer blockingDialTiming.Done()
|
||||||
network = "unix"
|
cc, err := grpcurl.BlockingDial(ctx, "", target, creds, opts...)
|
||||||
}
|
|
||||||
cc, err := grpcurl.BlockingDial(ctx, network, target, creds, opts...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fail(err, "Failed to dial target host %q", target)
|
fail(err, "Failed to dial target host %q", target)
|
||||||
}
|
}
|
||||||
|
|
@ -508,6 +609,7 @@ func main() {
|
||||||
refCtx := metadata.NewOutgoingContext(ctx, md)
|
refCtx := metadata.NewOutgoingContext(ctx, md)
|
||||||
cc = dial()
|
cc = dial()
|
||||||
refClient = grpcreflect.NewClientAuto(refCtx, cc)
|
refClient = grpcreflect.NewClientAuto(refCtx, cc)
|
||||||
|
refClient.AllowMissingFileDescriptors()
|
||||||
reflSource := grpcurl.DescriptorSourceFromServer(ctx, refClient)
|
reflSource := grpcurl.DescriptorSourceFromServer(ctx, refClient)
|
||||||
if fileSource != nil {
|
if fileSource != nil {
|
||||||
descSource = compositeSource{reflSource, fileSource}
|
descSource = compositeSource{reflSource, fileSource}
|
||||||
|
|
@ -552,6 +654,9 @@ func main() {
|
||||||
if err := writeProtoset(descSource, svcs...); err != nil {
|
if err := writeProtoset(descSource, svcs...); err != nil {
|
||||||
fail(err, "Failed to write protoset to %s", *protosetOut)
|
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 {
|
} else {
|
||||||
methods, err := grpcurl.ListMethods(descSource, symbol)
|
methods, err := grpcurl.ListMethods(descSource, symbol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -567,6 +672,9 @@ func main() {
|
||||||
if err := writeProtoset(descSource, symbol); err != nil {
|
if err := writeProtoset(descSource, symbol); err != nil {
|
||||||
fail(err, "Failed to write protoset to %s", *protosetOut)
|
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 {
|
} else if describe {
|
||||||
|
|
@ -671,6 +779,9 @@ func main() {
|
||||||
if err := writeProtoset(descSource, symbols...); err != nil {
|
if err := writeProtoset(descSource, symbols...); err != nil {
|
||||||
fail(err, "Failed to write protoset to %s", *protosetOut)
|
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 {
|
} else {
|
||||||
// Invoke an RPC
|
// Invoke an RPC
|
||||||
|
|
@ -703,7 +814,9 @@ func main() {
|
||||||
VerbosityLevel: verbosityLevel,
|
VerbosityLevel: verbosityLevel,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
invokeTiming := rootTiming.Child("InvokeRPC")
|
||||||
err = grpcurl.InvokeRPC(ctx, descSource, cc, symbol, append(addlHeaders, rpcHeaders...), h, rf.Next)
|
err = grpcurl.InvokeRPC(ctx, descSource, cc, symbol, append(addlHeaders, rpcHeaders...), h, rf.Next)
|
||||||
|
invokeTiming.Done()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errStatus, ok := status.FromError(err); ok && *formatError {
|
if errStatus, ok := status.FromError(err); ok && *formatError {
|
||||||
h.Status = errStatus
|
h.Status = errStatus
|
||||||
|
|
@ -734,6 +847,17 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dumpTiming(td *timingData, lvl int) {
|
||||||
|
var ind strings.Builder
|
||||||
|
for x := 0; x < lvl; x++ {
|
||||||
|
ind.WriteString(" ")
|
||||||
|
}
|
||||||
|
fmt.Printf("%s%s: %s\n", ind.String(), td.Title, td.Value)
|
||||||
|
for _, sd := range td.Sub {
|
||||||
|
dumpTiming(sd, lvl+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
fmt.Fprintf(os.Stderr, `Usage:
|
fmt.Fprintf(os.Stderr, `Usage:
|
||||||
%s [flags] [address] [list|describe] [symbol]
|
%s [flags] [address] [list|describe] [symbol]
|
||||||
|
|
@ -781,7 +905,7 @@ func prettify(docString string) string {
|
||||||
j++
|
j++
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join(parts[:j], "\n"+indent())
|
return strings.Join(parts[:j], "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func warn(msg string, args ...interface{}) {
|
func warn(msg string, args ...interface{}) {
|
||||||
|
|
@ -817,6 +941,13 @@ func writeProtoset(descSource grpcurl.DescriptorSource, symbols ...string) error
|
||||||
return grpcurl.WriteProtoset(f, descSource, symbols...)
|
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 {
|
type optionalBoolFlag struct {
|
||||||
set, val bool
|
set, val bool
|
||||||
}
|
}
|
||||||
|
|
@ -841,3 +972,12 @@ func (f *optionalBoolFlag) Set(s string) error {
|
||||||
func (f *optionalBoolFlag) IsBoolFlag() bool {
|
func (f *optionalBoolFlag) IsBoolFlag() bool {
|
||||||
return true
|
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)
|
||||||
|
}
|
||||||
|
|
|
||||||
101
desc_source.go
101
desc_source.go
|
|
@ -5,13 +5,15 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import this because it appears in exported API
|
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import these because some of their types appear in exported API
|
||||||
"github.com/jhump/protoreflect/desc"
|
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above
|
||||||
"github.com/jhump/protoreflect/desc/protoparse"
|
"github.com/jhump/protoreflect/desc/protoparse" //lint:ignore SA1019 same as above
|
||||||
"github.com/jhump/protoreflect/dynamic"
|
"github.com/jhump/protoreflect/desc/protoprint"
|
||||||
|
"github.com/jhump/protoreflect/dynamic" //lint:ignore SA1019 same as above
|
||||||
"github.com/jhump/protoreflect/grpcreflect"
|
"github.com/jhump/protoreflect/grpcreflect"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
|
|
@ -41,7 +43,7 @@ type DescriptorSource interface {
|
||||||
func DescriptorSourceFromProtoSets(fileNames ...string) (DescriptorSource, error) {
|
func DescriptorSourceFromProtoSets(fileNames ...string) (DescriptorSource, error) {
|
||||||
files := &descriptorpb.FileDescriptorSet{}
|
files := &descriptorpb.FileDescriptorSet{}
|
||||||
for _, fileName := range fileNames {
|
for _, fileName := range fileNames {
|
||||||
b, err := ioutil.ReadFile(fileName)
|
b, err := os.ReadFile(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not load protoset file %q: %v", fileName, err)
|
return nil, fmt.Errorf("could not load protoset file %q: %v", fileName, err)
|
||||||
}
|
}
|
||||||
|
|
@ -258,19 +260,9 @@ func reflectionSupport(err error) error {
|
||||||
// given output. The output will include descriptors for all files in which the
|
// given output. The output will include descriptors for all files in which the
|
||||||
// symbols are defined as well as their transitive dependencies.
|
// symbols are defined as well as their transitive dependencies.
|
||||||
func WriteProtoset(out io.Writer, descSource DescriptorSource, symbols ...string) error {
|
func WriteProtoset(out io.Writer, descSource DescriptorSource, symbols ...string) error {
|
||||||
// compute set of file descriptors
|
filenames, fds, err := getFileDescriptors(symbols, descSource)
|
||||||
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to find descriptor for %q: %v", sym, err)
|
return 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
|
// now expand that to include transitive dependencies in topologically sorted
|
||||||
// order (such that file always appears after its dependencies)
|
// order (such that file always appears after its dependencies)
|
||||||
|
|
@ -302,3 +294,76 @@ func addFilesToSet(allFiles []*descriptorpb.FileDescriptorProto, expanded map[st
|
||||||
}
|
}
|
||||||
return append(allFiles, fd.AsFileDescriptorProto())
|
return append(allFiles, fd.AsFileDescriptorProto())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteProtoFiles will use the given descriptor source to resolve all the given
|
||||||
|
// symbols and write proto files with their definitions to the given output directory.
|
||||||
|
func WriteProtoFiles(outProtoDirPath string, descSource DescriptorSource, symbols ...string) error {
|
||||||
|
filenames, fds, err := getFileDescriptors(symbols, descSource)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 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))
|
||||||
|
allFileDescriptors := make([]*desc.FileDescriptor, 0, len(fds))
|
||||||
|
for _, filename := range filenames {
|
||||||
|
allFileDescriptors = addFilesToFileDescriptorList(allFileDescriptors, expandedFiles, fds[filename])
|
||||||
|
}
|
||||||
|
pr := protoprint.Printer{}
|
||||||
|
// now we can serialize to files
|
||||||
|
for i := range allFileDescriptors {
|
||||||
|
if err := writeProtoFile(outProtoDirPath, allFileDescriptors[i], &pr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeProtoFile(outProtoDirPath string, fd *desc.FileDescriptor, pr *protoprint.Printer) error {
|
||||||
|
outFile := filepath.Join(outProtoDirPath, fd.GetFullyQualifiedName())
|
||||||
|
outDir := filepath.Dir(outFile)
|
||||||
|
if err := os.MkdirAll(outDir, 0777); err != nil {
|
||||||
|
return fmt.Errorf("failed to create directory %q: %w", outDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(outFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create proto file %q: %w", outFile, err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if err := pr.PrintProtoFile(fd, f); err != nil {
|
||||||
|
return fmt.Errorf("failed to write proto file %q: %w", outFile, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFileDescriptors(symbols []string, descSource DescriptorSource) ([]string, map[string]*desc.FileDescriptor, 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 nil, nil, 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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filenames, fds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFilesToFileDescriptorList(allFiles []*desc.FileDescriptor, expanded map[string]struct{}, fd *desc.FileDescriptor) []*desc.FileDescriptor {
|
||||||
|
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 = addFilesToFileDescriptorList(allFiles, expanded, dep)
|
||||||
|
}
|
||||||
|
return append(allFiles, fd)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package grpcurl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import this because it appears in exported API
|
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import this because it appears in exported API
|
||||||
|
|
@ -34,7 +34,7 @@ func TestWriteProtoset(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadProtoset(path string) (*descriptorpb.FileDescriptorSet, error) {
|
func loadProtoset(path string) (*descriptorpb.FileDescriptorSet, error) {
|
||||||
b, err := ioutil.ReadFile(path)
|
b, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import this because it appears in exported API
|
"github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import these because some of their types appear in exported API
|
||||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import this because it appears in exported API
|
"github.com/golang/protobuf/proto" //lint:ignore SA1019 same as above
|
||||||
"github.com/jhump/protoreflect/desc"
|
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above
|
||||||
"github.com/jhump/protoreflect/dynamic"
|
"github.com/jhump/protoreflect/dynamic" //lint:ignore SA1019 same as above
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import this because it appears in exported API
|
"github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import these because some of their types appear in exported API
|
||||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import this because it appears in exported API
|
"github.com/golang/protobuf/proto" //lint:ignore SA1019 same as above
|
||||||
"github.com/jhump/protoreflect/desc"
|
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
45
go.mod
45
go.mod
|
|
@ -1,31 +1,32 @@
|
||||||
module github.com/fullstorydev/grpcurl
|
module github.com/fullstorydev/grpcurl
|
||||||
|
|
||||||
go 1.18
|
go 1.24.0
|
||||||
|
|
||||||
|
toolchain go1.24.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/golang/protobuf v1.5.3
|
github.com/golang/protobuf v1.5.4
|
||||||
github.com/jhump/protoreflect v1.15.2
|
github.com/jhump/protoreflect v1.18.0
|
||||||
google.golang.org/grpc v1.57.0
|
google.golang.org/grpc v1.66.2
|
||||||
google.golang.org/protobuf v1.31.0
|
google.golang.org/protobuf v1.36.11
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/compute v1.19.1 // indirect
|
cel.dev/expr v0.15.0 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||||
github.com/bufbuild/protocompile v0.6.0 // indirect
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
|
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe // indirect
|
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect
|
||||||
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // indirect
|
github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 // indirect
|
||||||
github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f // indirect
|
github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.10.1 // indirect
|
github.com/jhump/protoreflect/v2 v2.0.0-beta.1 // indirect
|
||||||
golang.org/x/net v0.9.0 // indirect
|
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 // indirect
|
||||||
golang.org/x/oauth2 v0.7.0 // indirect
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||||
golang.org/x/sync v0.3.0 // indirect
|
golang.org/x/net v0.38.0 // indirect
|
||||||
golang.org/x/sys v0.7.0 // indirect
|
golang.org/x/oauth2 v0.27.0 // indirect
|
||||||
golang.org/x/text v0.9.0 // indirect
|
golang.org/x/sync v0.12.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 // indirect
|
golang.org/x/text v0.23.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
150
go.sum
150
go.sum
|
|
@ -1,106 +1,56 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w=
|
||||||
cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
|
cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg=
|
||||||
cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
|
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
|
||||||
github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY=
|
|
||||||
github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
|
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
|
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw=
|
||||||
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk=
|
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||||
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f h1:7T++XKzy4xg7PKy+bM+Sa9/oe1OC88yz2hXQUISoXfA=
|
github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 h1:IgJPqnrlY2Mr4pYB6oaMKvFvwJ9H+X6CCY5x1vCTcpc=
|
||||||
github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q=
|
github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155/go.mod h1:5Wkq+JduFtdAXihLmeTJf+tRYIT4KBc2vPXDhwVo1pA=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.10.1 h1:c0g45+xCJhdgFGw7a5QAfdS4byAbud7miNWJ1WwEVf8=
|
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/jhump/protoreflect v1.18.0 h1:TOz0MSR/0JOZ5kECB/0ufGnC2jdsgZ123Rd/k4Z5/2w=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/jhump/protoreflect v1.18.0/go.mod h1:ezWcltJIVF4zYdIFM+D/sHV4Oh5LNU08ORzCGfwvTz8=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/jhump/protoreflect/v2 v2.0.0-beta.1 h1:Dw1rslK/VotaUGYsv53XVWITr+5RCPXfvvlGrM/+B6w=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/jhump/protoreflect/v2 v2.0.0-beta.1/go.mod h1:D9LBEowZyv8/iSu97FU2zmXG3JxVTmNw21mu63niFzU=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQRi6bFQ7ogNO0ltFT4PmtwTLW4W+14=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
|
||||||
github.com/jhump/protoreflect v1.15.2 h1:7YppbATX94jEt9KLAc5hICx4h6Yt3SaavhQRsIUEHP0=
|
|
||||||
github.com/jhump/protoreflect v1.15.2/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4Elxc8qKP+p1k=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo=
|
||||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU=
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
|
||||||
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
|
google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||||
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
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/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
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=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
|
||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 h1:9NWlQfY2ePejTmfwUH1OWwmznFa+0kKcHGPDvcPza9M=
|
|
||||||
google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 h1:m8v1xLLLzMe1m5P+gCTF8nJB9epwZQUBERm20Oy1poQ=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
|
||||||
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.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
|
|
||||||
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
||||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
|
||||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
|
|
|
||||||
70
grpcurl.go
70
grpcurl.go
|
|
@ -14,20 +14,22 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import this because it appears in exported API
|
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import these because some of their types appear in exported API
|
||||||
"github.com/jhump/protoreflect/desc"
|
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above
|
||||||
"github.com/jhump/protoreflect/desc/protoprint"
|
"github.com/jhump/protoreflect/desc/protoprint"
|
||||||
"github.com/jhump/protoreflect/dynamic"
|
"github.com/jhump/protoreflect/dynamic" //lint:ignore SA1019 same as above
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
xdsCredentials "google.golang.org/grpc/credentials/xds"
|
||||||
|
_ "google.golang.org/grpc/health" // import grpc/health to enable transparent client side checking
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
protov2 "google.golang.org/protobuf/proto"
|
protov2 "google.golang.org/protobuf/proto"
|
||||||
"google.golang.org/protobuf/types/descriptorpb"
|
"google.golang.org/protobuf/types/descriptorpb"
|
||||||
|
|
@ -449,12 +451,10 @@ func makeTemplate(md *desc.MessageDescriptor, path []*desc.MessageDescriptor) pr
|
||||||
dm := dynamic.NewMessage(md)
|
dm := dynamic.NewMessage(md)
|
||||||
|
|
||||||
// if the message is a recursive structure, we don't want to blow the stack
|
// if the message is a recursive structure, we don't want to blow the stack
|
||||||
for _, seen := range path {
|
if slices.Contains(path, md) {
|
||||||
if seen == md {
|
|
||||||
// already visited this type; avoid infinite recursion
|
// already visited this type; avoid infinite recursion
|
||||||
return dm
|
return dm
|
||||||
}
|
}
|
||||||
}
|
|
||||||
path = append(path, dm.GetMessageDescriptor())
|
path = append(path, dm.GetMessageDescriptor())
|
||||||
|
|
||||||
// for repeated fields, add a single element with default value
|
// for repeated fields, add a single element with default value
|
||||||
|
|
@ -544,7 +544,7 @@ func ClientTLSConfig(insecureSkipVerify bool, cacertFile, clientCertFile, client
|
||||||
} else if cacertFile != "" {
|
} else if cacertFile != "" {
|
||||||
// Create a certificate pool from the certificate authority
|
// Create a certificate pool from the certificate authority
|
||||||
certPool := x509.NewCertPool()
|
certPool := x509.NewCertPool()
|
||||||
ca, err := ioutil.ReadFile(cacertFile)
|
ca, err := os.ReadFile(cacertFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not read ca certificate: %v", err)
|
return nil, fmt.Errorf("could not read ca certificate: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -581,7 +581,7 @@ func ServerTransportCredentials(cacertFile, serverCertFile, serverKeyFile string
|
||||||
if cacertFile != "" {
|
if cacertFile != "" {
|
||||||
// Create a certificate pool from the certificate authority
|
// Create a certificate pool from the certificate authority
|
||||||
certPool := x509.NewCertPool()
|
certPool := x509.NewCertPool()
|
||||||
ca, err := ioutil.ReadFile(cacertFile)
|
ca, err := os.ReadFile(cacertFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not read ca certificate: %v", err)
|
return nil, fmt.Errorf("could not read ca certificate: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -608,7 +608,24 @@ func ServerTransportCredentials(cacertFile, serverCertFile, serverKeyFile string
|
||||||
// BlockingDial is a helper method to dial the given address, using optional TLS credentials,
|
// BlockingDial is a helper method to dial the given address, using optional TLS credentials,
|
||||||
// and blocking until the returned connection is ready. If the given credentials are nil, the
|
// and blocking until the returned connection is ready. If the given credentials are nil, the
|
||||||
// connection will be insecure (plain-text).
|
// connection will be insecure (plain-text).
|
||||||
|
// The network parameter should be left empty in most cases when your address is a RFC 3986
|
||||||
|
// compliant URI. The resolver from grpc-go will resolve the correct network type.
|
||||||
func BlockingDial(ctx context.Context, network, address string, creds credentials.TransportCredentials, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
|
func BlockingDial(ctx context.Context, network, address string, creds credentials.TransportCredentials, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
|
||||||
|
if creds == nil {
|
||||||
|
creds = insecure.NewCredentials()
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if strings.HasPrefix(address, "xds:///") {
|
||||||
|
// The xds:/// prefix is used to signal to the gRPC client to use an xDS server to resolve the
|
||||||
|
// target. The relevant credentials will be automatically pulled from the GRPC_XDS_BOOTSTRAP or
|
||||||
|
// GRPC_XDS_BOOTSTRAP_CONFIG env vars.
|
||||||
|
creds, err = xdsCredentials.NewClientCredentials(xdsCredentials.ClientOptions{FallbackCreds: creds})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// grpc.Dial doesn't provide any information on permanent connection errors (like
|
// grpc.Dial doesn't provide any information on permanent connection errors (like
|
||||||
// TLS handshake failures). So in order to provide good error messages, we need a
|
// TLS handshake failures). So in order to provide good error messages, we need a
|
||||||
// custom dialer that can provide that info. That means we manage the TLS handshake.
|
// custom dialer that can provide that info. That means we manage the TLS handshake.
|
||||||
|
|
@ -624,26 +641,38 @@ func BlockingDial(ctx context.Context, network, address string, creds credential
|
||||||
|
|
||||||
// custom credentials and dialer will notify on error via the
|
// custom credentials and dialer will notify on error via the
|
||||||
// writeResult function
|
// writeResult function
|
||||||
if creds != nil {
|
|
||||||
creds = &errSignalingCreds{
|
creds = &errSignalingCreds{
|
||||||
TransportCredentials: creds,
|
TransportCredentials: creds,
|
||||||
writeResult: writeResult,
|
writeResult: writeResult,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch network {
|
||||||
|
case "":
|
||||||
|
// no-op, use address as-is
|
||||||
|
case "tcp":
|
||||||
|
if strings.HasPrefix(address, "unix://") {
|
||||||
|
return nil, fmt.Errorf("tcp network type cannot use unix address %s", address)
|
||||||
}
|
}
|
||||||
|
case "unix":
|
||||||
|
if !strings.HasPrefix(address, "unix://") {
|
||||||
|
// prepend unix:// to the address if it's not already there
|
||||||
|
// this is to maintain backwards compatibility because the custom dialer is replaced by
|
||||||
|
// the default dialer in grpc-go.
|
||||||
|
// https://github.com/fullstorydev/grpcurl/pull/480
|
||||||
|
address = "unix://" + address
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// custom dialer for other networks
|
||||||
dialer := func(ctx context.Context, address string) (net.Conn, error) {
|
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 insecure.NewCredentials() dial transport
|
|
||||||
// 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)
|
conn, err := (&net.Dialer{}).DialContext(ctx, network, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// capture the error so we can provide a better message
|
||||||
writeResult(err)
|
writeResult(err)
|
||||||
}
|
}
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
|
opts = append([]grpc.DialOption{grpc.WithContextDialer(dialer)}, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
// Even with grpc.FailOnNonTempDialError, this call will usually timeout in
|
// 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
|
// the face of TLS handshake errors. So we can't rely on grpc.WithBlock() to
|
||||||
|
|
@ -655,13 +684,8 @@ func BlockingDial(ctx context.Context, network, address string, creds credential
|
||||||
opts = append([]grpc.DialOption{grpc.FailOnNonTempDialError(true)}, opts...)
|
opts = append([]grpc.DialOption{grpc.FailOnNonTempDialError(true)}, opts...)
|
||||||
// But we don't want caller to be able to override these two, so we put
|
// But we don't want caller to be able to override these two, so we put
|
||||||
// them *after* the explicitly provided options.
|
// them *after* the explicitly provided options.
|
||||||
opts = append(opts, grpc.WithBlock(), grpc.WithContextDialer(dialer))
|
opts = append(opts, grpc.WithBlock(), grpc.WithTransportCredentials(creds))
|
||||||
|
|
||||||
if creds == nil {
|
|
||||||
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
||||||
} else {
|
|
||||||
opts = append(opts, grpc.WithTransportCredentials(creds))
|
|
||||||
}
|
|
||||||
conn, err := grpc.DialContext(ctx, address, opts...)
|
conn, err := grpc.DialContext(ctx, address, opts...)
|
||||||
var res interface{}
|
var res interface{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import this because it appears in exported API
|
"github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import these because some of their types appear in exported API
|
||||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import this because it appears in exported API
|
"github.com/golang/protobuf/proto" //lint:ignore SA1019 same as above
|
||||||
"github.com/jhump/protoreflect/desc"
|
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above
|
||||||
"github.com/jhump/protoreflect/grpcreflect"
|
"github.com/jhump/protoreflect/grpcreflect"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
|
@ -130,7 +129,7 @@ type svr struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *svr) load() error {
|
func (s *svr) load() error {
|
||||||
accts, err := ioutil.ReadFile(s.datafile)
|
accts, err := os.ReadFile(s.datafile)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -162,7 +161,7 @@ func (s *svr) flush() {
|
||||||
|
|
||||||
if b, err := json.Marshal(accounts); err != nil {
|
if b, err := json.Marshal(accounts); err != nil {
|
||||||
grpclog.Errorf("failed to save data to %q", s.datafile)
|
grpclog.Errorf("failed to save data to %q", s.datafile)
|
||||||
} else if err := ioutil.WriteFile(s.datafile, b, 0666); err != nil {
|
} else if err := os.WriteFile(s.datafile, b, 0666); err != nil {
|
||||||
grpclog.Errorf("failed to save data to %q", s.datafile)
|
grpclog.Errorf("failed to save data to %q", s.datafile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import this because it appears in exported API
|
"github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import these because some of their types appear in exported API
|
||||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import this because it appears in exported API
|
"github.com/golang/protobuf/proto" //lint:ignore SA1019 same as above
|
||||||
"github.com/jhump/protoreflect/desc"
|
"github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above
|
||||||
"github.com/jhump/protoreflect/dynamic"
|
"github.com/jhump/protoreflect/dynamic" //lint:ignore SA1019 same as above
|
||||||
"github.com/jhump/protoreflect/dynamic/grpcdynamic"
|
"github.com/jhump/protoreflect/dynamic/grpcdynamic"
|
||||||
"github.com/jhump/protoreflect/grpcreflect"
|
"github.com/jhump/protoreflect/grpcreflect"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ The last step is to update the Homebrew recipe to use the latest version. First,
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# download the source archive from GitHub
|
# download the source archive from GitHub
|
||||||
URL=https://github.com/fullstorydev/grpcurl/archive/v2.3.4.tar.gz
|
URL=https://github.com/fullstorydev/grpcurl/archive/refs/tags/v2.3.4.tar.gz
|
||||||
curl -L -o tmp.tgz $URL
|
curl -L -o tmp.tgz $URL
|
||||||
# and compute the SHA
|
# and compute the SHA
|
||||||
SHA="$(sha256sum < tmp.tgz | awk '{ print $1 }')"
|
SHA="$(sha256sum < tmp.tgz | awk '{ print $1 }')"
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ rm VERSION
|
||||||
|
|
||||||
# Homebrew release
|
# Homebrew release
|
||||||
|
|
||||||
URL="https://github.com/fullstorydev/grpcurl/archive/${VERSION}.tar.gz"
|
URL="https://github.com/fullstorydev/grpcurl/archive/refs/tags/${VERSION}.tar.gz"
|
||||||
curl -L -o tmp.tgz "$URL"
|
curl -L -o tmp.tgz "$URL"
|
||||||
SHA="$(sha256sum < tmp.tgz | awk '{ print $1 }')"
|
SHA="$(sha256sum < tmp.tgz | awk '{ print $1 }')"
|
||||||
rm tmp.tgz
|
rm tmp.tgz
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# packing and releasing
|
||||||
|
To pack the current branch to a snap package:
|
||||||
|
|
||||||
|
`snapcraft pack`
|
||||||
|
|
||||||
|
To install the package locally:
|
||||||
|
|
||||||
|
`snap install ./grpcurl_v[version tag]_amd64.snap --devmode`
|
||||||
|
|
||||||
|
To upload the snap to the edge channel:
|
||||||
|
|
||||||
|
`snapcraft upload --release edge ./grpcurl_v[version tag]_amd64.snap`
|
||||||
|
|
||||||
|
(you need to own the package name registration for this!)
|
||||||
|
|
||||||
|
# ownership
|
||||||
|
The snap's current owner is `pietro.pasotti@canonical.com`; who is very happy to support with maintaining the snap distribution and/or transfer its ownership to the developers.
|
||||||
|
|
||||||
|
Please reach out to me for questions regarding the snap; including:
|
||||||
|
- adding support for other architectures
|
||||||
|
- automating the release
|
||||||
|
|
||||||
|
Cheers and thanks for the awesome tool!
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
name: grpcurl
|
||||||
|
base: core24
|
||||||
|
# allow grpcurl part to call craftctl set-version
|
||||||
|
adopt-info: grpcurl
|
||||||
|
summary: grpcurl is a command-line tool that lets you interact with gRPC servers.
|
||||||
|
|
||||||
|
description: |
|
||||||
|
grpcurl is a command-line tool that lets you interact with gRPC servers.
|
||||||
|
It's basically curl for gRPC servers.
|
||||||
|
|
||||||
|
grade: stable
|
||||||
|
confinement: strict
|
||||||
|
license: MIT
|
||||||
|
|
||||||
|
platforms:
|
||||||
|
amd64:
|
||||||
|
build-on:
|
||||||
|
- amd64
|
||||||
|
build-for:
|
||||||
|
- amd64
|
||||||
|
arm64:
|
||||||
|
build-on:
|
||||||
|
- amd64
|
||||||
|
- arm64
|
||||||
|
build-for:
|
||||||
|
- arm64
|
||||||
|
|
||||||
|
apps:
|
||||||
|
grpcurl:
|
||||||
|
command: grpcurl
|
||||||
|
plugs:
|
||||||
|
- network
|
||||||
|
|
||||||
|
parts:
|
||||||
|
grpcurl:
|
||||||
|
plugin: go
|
||||||
|
build-snaps: [go/latest/stable]
|
||||||
|
source: https://github.com/fullstorydev/grpcurl
|
||||||
|
source-type: git
|
||||||
|
override-build: |
|
||||||
|
tag="$(git describe --tags --abbrev=0)"
|
||||||
|
craftctl set version="$tag"
|
||||||
|
|
||||||
|
go build -o $CRAFT_PART_INSTALL/grpcurl ./cmd/grpcurl/grpcurl.go
|
||||||
|
|
||||||
|
# adjust the permissions
|
||||||
|
chmod 0755 $CRAFT_PART_INSTALL/grpcurl
|
||||||
|
|
@ -253,8 +253,12 @@ func TestBrokenTLS_ClientNotTrusted(t *testing.T) {
|
||||||
e.Close()
|
e.Close()
|
||||||
t.Fatal("expecting TLS failure setting up server and client")
|
t.Fatal("expecting TLS failure setting up server and client")
|
||||||
}
|
}
|
||||||
if !strings.Contains(err.Error(), "bad certificate") {
|
// Check for either the old error (Go <=1.24) or the new one (Go 1.25+)
|
||||||
t.Fatalf("expecting TLS certificate error, got: %v", err)
|
// Go 1.24: "bad certificate"
|
||||||
|
// Go 1.25: "handshake failure"
|
||||||
|
errMsg := err.Error()
|
||||||
|
if !strings.Contains(errMsg, "bad certificate") && !strings.Contains(errMsg, "handshake failure") {
|
||||||
|
t.Fatalf("expecting a specific TLS certificate or handshake error, got: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -293,8 +297,12 @@ func TestBrokenTLS_RequireClientCertButNonePresented(t *testing.T) {
|
||||||
e.Close()
|
e.Close()
|
||||||
t.Fatal("expecting TLS failure setting up server and client")
|
t.Fatal("expecting TLS failure setting up server and client")
|
||||||
}
|
}
|
||||||
if !strings.Contains(err.Error(), "bad certificate") {
|
// Check for either the old error (Go <=1.24) or the new one (Go 1.25+)
|
||||||
t.Fatalf("expecting TLS certificate error, got: %v", err)
|
// Go 1.24: "bad certificate"
|
||||||
|
// Go 1.25: "handshake failure"
|
||||||
|
errMsg := err.Error()
|
||||||
|
if !strings.Contains(errMsg, "bad certificate") && !strings.Contains(errMsg, "handshake failure") {
|
||||||
|
t.Fatalf("expecting a specific TLS certificate or handshake error, got: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue