mirror of
https://github.com/fullstorydev/grpcurl.git
synced 2026-09-18 08:18:33 +03:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cf21d4aca5 | |||
| 8568146e02 | |||
| 7884ecb9c6 | |||
| aba598c119 | |||
| 4bb0f337c7 | |||
| 5f2d0c2dfc | |||
| 6be36ba632 | |||
| 353acfef45 | |||
| 04bf23be33 | |||
| 1a48043e8a | |||
| afea969b8a | |||
| 4ea1554ec7 | |||
| 0521a49a6a | |||
| cb68aaa7f5 | |||
| 2922de784b | |||
| f30a5a5545 | |||
| 5725f04a83 | |||
| 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 |
+9
-18
@@ -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-19:
|
build-1-23:
|
||||||
working_directory: ~/repo
|
working_directory: ~/repo
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/go:1.19
|
- image: cimg/go:1.23
|
||||||
steps: *simple_job_steps
|
steps: *simple_job_steps
|
||||||
|
|
||||||
build-1-20:
|
build-1-24:
|
||||||
working_directory: ~/repo
|
working_directory: ~/repo
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/go:1.20
|
- image: cimg/go:1.24
|
||||||
steps: *simple_job_steps
|
steps: *simple_job_steps
|
||||||
|
|
||||||
build-1-21:
|
build-1-25:
|
||||||
working_directory: ~/repo
|
working_directory: ~/repo
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/go:1.21
|
- image: cimg/go:1.25
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
@@ -32,18 +32,9 @@ jobs:
|
|||||||
command: |
|
command: |
|
||||||
make ci
|
make ci
|
||||||
|
|
||||||
# TODO: Need updates to some static analyzer tools to support 1.22. After those
|
|
||||||
# are updated, move the full linting from 1.21 to this latest release.
|
|
||||||
build-1-22:
|
|
||||||
working_directory: ~/repo
|
|
||||||
docker:
|
|
||||||
- image: cimg/go:1.22
|
|
||||||
steps: *simple_job_steps
|
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
pr-build-test:
|
pr-build-test:
|
||||||
jobs:
|
jobs:
|
||||||
- build-1-19
|
- build-1-23
|
||||||
- build-1-20
|
- build-1-24
|
||||||
- build-1-21
|
- build-1-25
|
||||||
- build-1-22
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Controls how GitHub groups merged PRs in the auto-generated release notes.
|
||||||
|
# goreleaser delegates to GitHub's generator (changelog.use: github-native in
|
||||||
|
# .goreleaser.yml), so this file is what shapes the notes for a release.
|
||||||
|
#
|
||||||
|
# Labels are optional: anything unlabeled lands in "Changes". Labeling a PR
|
||||||
|
# `bug` or `enhancement` just sorts it into a nicer bucket, and dependabot
|
||||||
|
# applies `dependencies` on its own, which keeps version bumps out of the way
|
||||||
|
# at the bottom.
|
||||||
|
changelog:
|
||||||
|
exclude:
|
||||||
|
labels:
|
||||||
|
- ignore-for-release
|
||||||
|
categories:
|
||||||
|
- title: Enhancements
|
||||||
|
labels:
|
||||||
|
- enhancement
|
||||||
|
- title: Bug Fixes
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
- title: Dependencies
|
||||||
|
labels:
|
||||||
|
- dependencies
|
||||||
|
# Catch-all. Must stay last: a PR lands in the first category it matches.
|
||||||
|
- title: Changes
|
||||||
|
labels:
|
||||||
|
- "*"
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "Release version, sem-ver format: vM.N.P"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
dry_run:
|
||||||
|
description: "Build everything, publish nothing (aka DRY-RUN... no tag, no GitHub release, no image push, no brew PR)"
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
|
# Only one release may be in flight at a time.
|
||||||
|
concurrency:
|
||||||
|
group: release
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Tag, build binaries, and create the GitHub release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write # create the tag and the release
|
||||||
|
steps:
|
||||||
|
- name: Validate version
|
||||||
|
run: |
|
||||||
|
if [[ ! "${{ inputs.version }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo "::error::'${{ inputs.version }}' is not a sem-ver version of the form vM.N.P" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
# goreleaser needs full history (and existing tags) to compute the change log
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Check that the tag does not already exist
|
||||||
|
run: |
|
||||||
|
if git rev-parse -q --verify "refs/tags/${{ inputs.version }}" >/dev/null; then
|
||||||
|
echo "::error::tag ${{ inputs.version }} already exists" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v7
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
# Releases are effectively immutable, so make sure we aren't shipping a broken build.
|
||||||
|
- name: Run tests
|
||||||
|
run: go test ./...
|
||||||
|
|
||||||
|
- name: Create tag
|
||||||
|
run: |
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
git tag -a "${{ inputs.version }}" -m "Release ${{ inputs.version }}"
|
||||||
|
|
||||||
|
- name: Push tag
|
||||||
|
if: ${{ !inputs.dry_run }}
|
||||||
|
run: git push origin "${{ inputs.version }}"
|
||||||
|
|
||||||
|
# Cross-compiles for every platform in .goreleaser.yml, then creates the
|
||||||
|
# GitHub release with generated notes and uploads the archives.
|
||||||
|
- name: Build and release
|
||||||
|
run: make release GORELEASER_ARGS="${{ inputs.dry_run && '--skip=publish,announce' || '' }}"
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
# CHANGELOG.md is the release notes that *would* have been published,
|
||||||
|
# which is worth eyeballing before a real release.
|
||||||
|
- name: Upload artifacts for inspection
|
||||||
|
if: ${{ inputs.dry_run }}
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: dry-run-dist
|
||||||
|
path: |
|
||||||
|
dist/*.tar.gz
|
||||||
|
dist/*.zip
|
||||||
|
dist/*.txt
|
||||||
|
dist/*.deb
|
||||||
|
dist/*.rpm
|
||||||
|
dist/CHANGELOG.md
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
docker:
|
||||||
|
name: Build and push the Docker image
|
||||||
|
needs: release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write # push to ghcr.io
|
||||||
|
outputs:
|
||||||
|
image: ${{ steps.image.outputs.name }}
|
||||||
|
env:
|
||||||
|
# Matches what releasing/do-release.sh has always published.
|
||||||
|
PLATFORMS: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le
|
||||||
|
steps:
|
||||||
|
# Derived from the repo rather than hardcoded, so this works the same on a
|
||||||
|
# fork: `packages: write` only grants access to packages owned by the repo
|
||||||
|
# owner. GHCR also rejects uppercase, and GitHub expressions have no
|
||||||
|
# lowercase function, hence the shell parameter expansion.
|
||||||
|
- name: Compute image name
|
||||||
|
id: image
|
||||||
|
run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
# the release job just created this tag; build the image from exactly that source
|
||||||
|
ref: ${{ inputs.dry_run && github.sha || inputs.version }}
|
||||||
|
|
||||||
|
# The Dockerfile stamps the binary with the contents of this file. It is
|
||||||
|
# gitignored, and produced by `make docker` during local builds.
|
||||||
|
- name: Write VERSION file
|
||||||
|
run: echo "${{ inputs.version }}" > VERSION
|
||||||
|
|
||||||
|
- uses: docker/setup-qemu-action@v4
|
||||||
|
- uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
if: ${{ !inputs.dry_run }}
|
||||||
|
uses: docker/login-action@v4
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
# The Dockerfile has two final stages: the default `scratch` image and an
|
||||||
|
# `alpine` one. Both are published, as do-release.sh does.
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: ${{ env.PLATFORMS }}
|
||||||
|
push: ${{ !inputs.dry_run }}
|
||||||
|
no-cache: true
|
||||||
|
tags: |
|
||||||
|
${{ steps.image.outputs.name }}:${{ inputs.version }}
|
||||||
|
${{ steps.image.outputs.name }}:latest
|
||||||
|
|
||||||
|
- name: Build and push (alpine)
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
target: alpine
|
||||||
|
platforms: ${{ env.PLATFORMS }}
|
||||||
|
push: ${{ !inputs.dry_run }}
|
||||||
|
no-cache: true
|
||||||
|
tags: |
|
||||||
|
${{ steps.image.outputs.name }}:${{ inputs.version }}-alpine
|
||||||
|
${{ steps.image.outputs.name }}:latest-alpine
|
||||||
|
|
||||||
|
summary:
|
||||||
|
name: Summarize
|
||||||
|
needs: [release, docker]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Dry run summary
|
||||||
|
if: ${{ inputs.dry_run }}
|
||||||
|
run: |
|
||||||
|
{
|
||||||
|
echo "## Dry run of ${{ inputs.version }} succeeded"
|
||||||
|
echo
|
||||||
|
echo "Nothing was published. The binaries are attached to this run as the \`dry-run-dist\` artifact."
|
||||||
|
echo "Re-run with **dry_run** unchecked to publish."
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
- name: Release summary
|
||||||
|
if: ${{ !inputs.dry_run }}
|
||||||
|
run: |
|
||||||
|
{
|
||||||
|
echo "## Released ${{ inputs.version }}"
|
||||||
|
echo
|
||||||
|
echo "* [GitHub release](${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ inputs.version }})"
|
||||||
|
echo "* Image \`${{ needs.docker.outputs.image }}:${{ inputs.version }}\` (and \`:latest\`)"
|
||||||
|
echo "* Image \`${{ needs.docker.outputs.image }}:${{ inputs.version }}-alpine\` (and \`:latest-alpine\`)"
|
||||||
|
echo
|
||||||
|
echo "Release notes were generated from the merged PRs. Nothing else to do."
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
dist/
|
dist/
|
||||||
|
.claude/
|
||||||
.idea/
|
.idea/
|
||||||
VERSION
|
VERSION
|
||||||
.tmp/
|
.tmp/
|
||||||
|
*.snap
|
||||||
|
|||||||
+13
-1
@@ -1,3 +1,5 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
builds:
|
builds:
|
||||||
- binary: grpcurl
|
- binary: grpcurl
|
||||||
main: ./cmd/grpcurl
|
main: ./cmd/grpcurl
|
||||||
@@ -8,14 +10,21 @@ 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: darwin
|
||||||
|
goarch: arm
|
||||||
- goos: windows
|
- goos: windows
|
||||||
goarch: arm64
|
goarch: arm
|
||||||
- goos: darwin
|
- goos: darwin
|
||||||
goarch: s390x
|
goarch: s390x
|
||||||
- goos: windows
|
- goos: windows
|
||||||
@@ -52,3 +61,6 @@ nfpms:
|
|||||||
formats:
|
formats:
|
||||||
- deb
|
- deb
|
||||||
- rpm
|
- rpm
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
use: github-native
|
||||||
|
|||||||
+3
-3
@@ -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
|
||||||
|
|||||||
@@ -1,11 +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
|
# Disable CGO for improved compatibility across distros
|
||||||
export CGO_ENABLED=0
|
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
|
||||||
@@ -29,10 +29,14 @@ updatedeps:
|
|||||||
install:
|
install:
|
||||||
go install -ldflags '-X "main.version=dev build $(dev_build_version)"' ./...
|
go install -ldflags '-X "main.version=dev build $(dev_build_version)"' ./...
|
||||||
|
|
||||||
|
GORELEASER_VERSION := v2.5.0
|
||||||
|
# Extra flags for goreleaser, e.g. "--skip=publish" for a dry run.
|
||||||
|
GORELEASER_ARGS ?=
|
||||||
|
|
||||||
.PHONY: release
|
.PHONY: release
|
||||||
release:
|
release:
|
||||||
@go install github.com/goreleaser/goreleaser@v1.21.0
|
@go install github.com/goreleaser/goreleaser/v2@$(GORELEASER_VERSION)
|
||||||
goreleaser release --clean
|
goreleaser release --clean $(GORELEASER_ARGS)
|
||||||
|
|
||||||
.PHONY: docker
|
.PHONY: docker
|
||||||
docker:
|
docker:
|
||||||
@@ -44,13 +48,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
|
||||||
|
|
||||||
@@ -67,8 +73,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:
|
||||||
@@ -77,7 +83,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.
|
||||||
@@ -93,8 +99,7 @@ errcheck:
|
|||||||
errcheck ./...
|
errcheck ./...
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test: deps
|
||||||
# The race detector requires CGO: https://github.com/golang/go/issues/6508
|
|
||||||
CGO_ENABLED=1 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
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -61,9 +62,9 @@ brew install grpcurl
|
|||||||
For platforms that support Docker, you can download an image that lets you run `grpcurl`:
|
For platforms that support Docker, you can download an image that lets you run `grpcurl`:
|
||||||
```shell
|
```shell
|
||||||
# Download image
|
# Download image
|
||||||
docker pull fullstorydev/grpcurl:latest
|
docker pull ghcr.io/fullstorydev/grpcurl:latest
|
||||||
# Run the tool
|
# Run the tool
|
||||||
docker run fullstorydev/grpcurl api.grpc.me:443 list
|
docker run ghcr.io/fullstorydev/grpcurl api.grpc.me:443 list
|
||||||
```
|
```
|
||||||
Note that there are some pitfalls when using docker:
|
Note that there are some pitfalls when using docker:
|
||||||
- If you need to interact with a server listening on the host's loopback network, you must specify the host as `host.docker.internal` instead of `localhost` (for Mac or Windows) _OR_ have the container use the host network with `-network="host"` (Linux only).
|
- If you need to interact with a server listening on the host's loopback network, you must specify the host as `host.docker.internal` instead of `localhost` (for Mac or Windows) _OR_ have the container use the host network with `-network="host"` (Linux only).
|
||||||
@@ -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`:
|
||||||
@@ -139,12 +146,19 @@ grpcurl -d @ grpc.server.com:443 my.custom.server.Service/Method <<EOM
|
|||||||
{
|
{
|
||||||
"id": 1234,
|
"id": 1234,
|
||||||
"tags": [
|
"tags": [
|
||||||
"foor",
|
"foo",
|
||||||
"bar"
|
"bar"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
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:
|
||||||
|
|||||||
+49
-15
@@ -8,6 +8,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -33,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>"
|
||||||
@@ -151,6 +152,14 @@ 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(`
|
||||||
@@ -449,7 +458,7 @@ 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()
|
||||||
@@ -460,13 +469,13 @@ func main() {
|
|||||||
defer dialTiming.Done()
|
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,
|
||||||
@@ -475,12 +484,12 @@ func main() {
|
|||||||
if *maxMsgSz > 0 {
|
if *maxMsgSz > 0 {
|
||||||
opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(*maxMsgSz)))
|
opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(*maxMsgSz)))
|
||||||
}
|
}
|
||||||
network := "tcp"
|
if isUnixSocket != nil && isUnixSocket() && !strings.HasPrefix(target, "unix://") {
|
||||||
if isUnixSocket != nil && isUnixSocket() {
|
// prepend unix:// to the address if it's not already there
|
||||||
network = "unix"
|
// this is to maintain backwards compatibility because the custom dialer is replaced by
|
||||||
if *authority == "" {
|
// the default dialer in grpc-go.
|
||||||
*authority = "localhost"
|
// https://github.com/fullstorydev/grpcurl/pull/480
|
||||||
}
|
target = "unix://" + target
|
||||||
}
|
}
|
||||||
var creds credentials.TransportCredentials
|
var creds credentials.TransportCredentials
|
||||||
if *plaintext {
|
if *plaintext {
|
||||||
@@ -548,7 +557,7 @@ func main() {
|
|||||||
|
|
||||||
blockingDialTiming := dialTiming.Child("BlockingDial")
|
blockingDialTiming := dialTiming.Child("BlockingDial")
|
||||||
defer blockingDialTiming.Done()
|
defer blockingDialTiming.Done()
|
||||||
cc, err := grpcurl.BlockingDial(ctx, network, target, creds, opts...)
|
cc, err := grpcurl.BlockingDial(ctx, "", 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)
|
||||||
}
|
}
|
||||||
@@ -645,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 {
|
||||||
@@ -660,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 {
|
||||||
@@ -764,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
|
||||||
@@ -830,11 +848,11 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dumpTiming(td *timingData, lvl int) {
|
func dumpTiming(td *timingData, lvl int) {
|
||||||
ind := ""
|
var ind strings.Builder
|
||||||
for x := 0; x < lvl; x++ {
|
for x := 0; x < lvl; x++ {
|
||||||
ind += " "
|
ind.WriteString(" ")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s%s: %s\n", ind, td.Title, td.Value)
|
fmt.Printf("%s%s: %s\n", ind.String(), td.Title, td.Value)
|
||||||
for _, sd := range td.Sub {
|
for _, sd := range td.Sub {
|
||||||
dumpTiming(sd, lvl+1)
|
dumpTiming(sd, lvl+1)
|
||||||
}
|
}
|
||||||
@@ -923,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
|
||||||
}
|
}
|
||||||
@@ -947,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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows
|
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows
|
||||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris windows
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
+92
-14
@@ -6,11 +6,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"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"
|
||||||
"github.com/jhump/protoreflect/desc"
|
"github.com/jhump/protoreflect/desc"
|
||||||
"github.com/jhump/protoreflect/desc/protoparse"
|
"github.com/jhump/protoreflect/desc/protoparse"
|
||||||
|
"github.com/jhump/protoreflect/desc/protoprint"
|
||||||
"github.com/jhump/protoreflect/dynamic"
|
"github.com/jhump/protoreflect/dynamic"
|
||||||
"github.com/jhump/protoreflect/grpcreflect"
|
"github.com/jhump/protoreflect/grpcreflect"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
@@ -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))
|
if err != nil {
|
||||||
fds := make(map[string]*desc.FileDescriptor, len(symbols))
|
return err
|
||||||
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
|
// 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,89 @@ 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])
|
||||||
|
}
|
||||||
|
return writeProtoFiles(outProtoDirPath, allFileDescriptors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeProtoFiles(outProtoDirPath string, allFileDescriptors []*desc.FileDescriptor) error {
|
||||||
|
if err := os.MkdirAll(outProtoDirPath, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create output directory %q: %w", outProtoDirPath, err)
|
||||||
|
}
|
||||||
|
root, err := os.OpenRoot(outProtoDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open output directory %q: %w", outProtoDirPath, err)
|
||||||
|
}
|
||||||
|
defer root.Close()
|
||||||
|
pr := protoprint.Printer{}
|
||||||
|
// now we can serialize to files
|
||||||
|
for i := range allFileDescriptors {
|
||||||
|
if err := writeProtoFile(root, allFileDescriptors[i], &pr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeProtoFile(root *os.Root, fd *desc.FileDescriptor, pr *protoprint.Printer) error {
|
||||||
|
// root confines all path operations to the output directory, so a
|
||||||
|
// malicious file name (e.g. "../escape.proto") cannot write outside it
|
||||||
|
outFile := fd.GetFullyQualifiedName()
|
||||||
|
if err := root.MkdirAll(filepath.Dir(outFile), 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create directory for %q: %w", outFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := root.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)
|
||||||
|
}
|
||||||
|
|||||||
+47
-1
@@ -3,9 +3,11 @@ package grpcurl
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"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"
|
||||||
|
"github.com/jhump/protoreflect/desc"
|
||||||
"google.golang.org/protobuf/types/descriptorpb"
|
"google.golang.org/protobuf/types/descriptorpb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,3 +62,47 @@ func checkWriteProtoset(t *testing.T, descSrc DescriptorSource, protoset *descri
|
|||||||
t.Fatalf("written protoset not equal to input:\nExpecting: %s\nActual: %s", protoset, &result)
|
t.Fatalf("written protoset not equal to input:\nExpecting: %s\nActual: %s", protoset, &result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeProtoFileForTest(t *testing.T, dir, fdName string) error {
|
||||||
|
t.Helper()
|
||||||
|
fd, err := desc.CreateFileDescriptor(&descriptorpb.FileDescriptorProto{
|
||||||
|
Name: proto.String(fdName),
|
||||||
|
Syntax: proto.String("proto3"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create file descriptor: %v", err)
|
||||||
|
}
|
||||||
|
return writeProtoFiles(dir, []*desc.FileDescriptor{fd})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteProtoFile_NormalPath(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
if err := writeProtoFileForTest(t, dir, "foo/bar.proto"); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(dir, "foo", "bar.proto")); err != nil {
|
||||||
|
t.Fatalf("expected output file not created: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteProtoFile_RejectsPathTraversal(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
if err := writeProtoFileForTest(t, dir, "../escape.proto"); err == nil {
|
||||||
|
t.Fatal("expected error for path-traversing descriptor name, got nil")
|
||||||
|
}
|
||||||
|
escapePath := filepath.Join(dir, "..", "escape.proto")
|
||||||
|
if _, err := os.Stat(escapePath); err == nil {
|
||||||
|
t.Fatalf("file was created outside output directory at %q", escapePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteProtoFile_RejectsDeepPathTraversal(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
if err := writeProtoFileForTest(t, dir, "foo/../../../escape.proto"); err == nil {
|
||||||
|
t.Fatal("expected error for path-traversing descriptor name, got nil")
|
||||||
|
}
|
||||||
|
escapePath := filepath.Join(dir, "foo", "..", "..", "..", "escape.proto")
|
||||||
|
if _, err := os.Stat(escapePath); err == nil {
|
||||||
|
t.Fatalf("file was created outside output directory at %q", escapePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ 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"
|
||||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import this because it appears in exported API
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/jhump/protoreflect/desc"
|
"github.com/jhump/protoreflect/desc"
|
||||||
"github.com/jhump/protoreflect/dynamic"
|
"github.com/jhump/protoreflect/dynamic"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
@@ -330,7 +330,6 @@ func (r anyResolverWithFallback) Resolve(typeUrl string) (proto.Message, error)
|
|||||||
if slash := strings.LastIndex(mname, "/"); slash >= 0 {
|
if slash := strings.LastIndex(mname, "/"); slash >= 0 {
|
||||||
mname = mname[slash+1:]
|
mname = mname[slash+1:]
|
||||||
}
|
}
|
||||||
//lint:ignore SA1019 new non-deprecated API requires other code changes; deferring...
|
|
||||||
mt := proto.MessageType(mname)
|
mt := proto.MessageType(mname)
|
||||||
if mt != nil {
|
if mt != nil {
|
||||||
return reflect.New(mt.Elem()).Interface().(proto.Message), nil
|
return reflect.New(mt.Elem()).Interface().(proto.Message), nil
|
||||||
|
|||||||
+2
-2
@@ -7,8 +7,8 @@ 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"
|
||||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import this because it appears in exported API
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/jhump/protoreflect/desc"
|
"github.com/jhump/protoreflect/desc"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
|||||||
@@ -1,31 +1,44 @@
|
|||||||
module github.com/fullstorydev/grpcurl
|
module github.com/fullstorydev/grpcurl
|
||||||
|
|
||||||
go 1.19
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/golang/protobuf v1.5.4
|
github.com/golang/protobuf v1.5.4
|
||||||
github.com/jhump/protoreflect v1.16.0
|
github.com/jhump/protoreflect v1.18.1
|
||||||
google.golang.org/grpc v1.61.0
|
google.golang.org/grpc v1.83.2
|
||||||
google.golang.org/protobuf v1.33.1-0.20240408130810-98873a205002
|
google.golang.org/protobuf v1.36.12
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/compute v1.23.3 // indirect
|
cel.dev/expr v0.25.2 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
cloud.google.com/go/auth v0.18.2 // indirect
|
||||||
github.com/bufbuild/protocompile v0.10.0 // indirect
|
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||||
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect
|
||||||
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe // indirect
|
github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect
|
||||||
github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 // indirect
|
github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
|
||||||
github.com/envoyproxy/go-control-plane v0.11.1 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect
|
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||||
golang.org/x/net v0.22.0 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
golang.org/x/oauth2 v0.14.0 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
golang.org/x/sync v0.6.0 // indirect
|
github.com/google/s2a-go v0.1.9 // indirect
|
||||||
golang.org/x/sys v0.18.0 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
github.com/jhump/protoreflect/v2 v2.0.0-beta.1 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
github.com/spiffe/go-spiffe/v2 v2.7.0 // indirect
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.44.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.44.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.44.0 // indirect
|
||||||
|
golang.org/x/crypto v0.55.0 // indirect
|
||||||
|
golang.org/x/net v0.58.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.36.0 // indirect
|
||||||
|
golang.org/x/sync v0.22.0 // indirect
|
||||||
|
golang.org/x/sys v0.47.0 // indirect
|
||||||
|
golang.org/x/text v0.41.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,118 +1,95 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=
|
||||||
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||||
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
|
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
|
||||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||||
github.com/bufbuild/protocompile v0.10.0 h1:+jW/wnLMLxaCEG8AX9lD0bQ5v9h1RUiMKOBOT5ll9dM=
|
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
|
||||||
github.com/bufbuild/protocompile v0.10.0/go.mod h1:G9qQIQo0xZ6Uyj6CMNz0saGmx2so+KONo8/KrELABiY=
|
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
|
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk=
|
|
||||||
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-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/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 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1MyGqJ2DIk5HpM=
|
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
|
||||||
github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g=
|
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
|
github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
|
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||||
|
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||||
github.com/jhump/protoreflect v1.16.0 h1:54fZg+49widqXYQ0b+usAFHbMkBGR4PpXrsHc8+TBDg=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/jhump/protoreflect v1.16.0/go.mod h1:oYPd7nPvcBw/5wlDfm/AVmU9zH9BgqGCI469pGxfj/8=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
|
||||||
|
github.com/jhump/protoreflect v1.18.1 h1:h4odAaLg9wyn7yHxMF7sSkJ7JfLwK1oy37/1Pi212GE=
|
||||||
|
github.com/jhump/protoreflect v1.18.1/go.mod h1:I2yar2oJEMf0k4EMryPzfV0tvGwN/SejJziYBOpETQo=
|
||||||
|
github.com/jhump/protoreflect/v2 v2.0.0-beta.1 h1:Dw1rslK/VotaUGYsv53XVWITr+5RCPXfvvlGrM/+B6w=
|
||||||
|
github.com/jhump/protoreflect/v2 v2.0.0-beta.1/go.mod h1:D9LBEowZyv8/iSu97FU2zmXG3JxVTmNw21mu63niFzU=
|
||||||
|
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQRi6bFQ7ogNO0ltFT4PmtwTLW4W+14=
|
||||||
|
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||||
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||||
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
|
||||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||||
golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
|
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
||||||
golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
|
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
|
||||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
|
||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 h1:1VUiZAXyC+zmiFYi+WLtBzr68Cj8wOofHjjrA/kkizc=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|
||||||
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/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
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.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
|
||||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
|
||||||
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-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
|
|
||||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
|
|
||||||
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.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
|
|
||||||
google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
|
||||||
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.33.1-0.20240408130810-98873a205002 h1:V7Da7qt0MkY3noVANIMVBk28nOnijADeOR3i5Hcvpj4=
|
|
||||||
google.golang.org/protobuf v1.33.1-0.20240408130810-98873a205002/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
|
||||||
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=
|
|
||||||
|
|||||||
+142
-43
@@ -17,17 +17,22 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import this because it appears in exported API
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/jhump/protoreflect/desc"
|
"github.com/jhump/protoreflect/desc"
|
||||||
"github.com/jhump/protoreflect/desc/protoprint"
|
"github.com/jhump/protoreflect/desc/protoprint"
|
||||||
"github.com/jhump/protoreflect/dynamic"
|
"github.com/jhump/protoreflect/dynamic"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
"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"
|
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,11 +454,9 @@ 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())
|
||||||
|
|
||||||
@@ -567,9 +570,6 @@ func ClientTLSConfig(insecureSkipVerify bool, cacertFile, clientCertFile, client
|
|||||||
// client certs. The serverCertFile and serverKeyFile must both not be blank.
|
// client certs. The serverCertFile and serverKeyFile must both not be blank.
|
||||||
func ServerTransportCredentials(cacertFile, serverCertFile, serverKeyFile string, requireClientCerts bool) (credentials.TransportCredentials, error) {
|
func ServerTransportCredentials(cacertFile, serverCertFile, serverKeyFile string, requireClientCerts bool) (credentials.TransportCredentials, error) {
|
||||||
var tlsConf tls.Config
|
var tlsConf tls.Config
|
||||||
// TODO(jh): Remove this line once https://github.com/golang/go/issues/28779 is fixed
|
|
||||||
// in Go tip. Until then, the recently merged TLS 1.3 support breaks the TLS tests.
|
|
||||||
tlsConf.MaxVersion = tls.VersionTLS12
|
|
||||||
|
|
||||||
// Load the server certificates from disk
|
// Load the server certificates from disk
|
||||||
certificate, err := tls.LoadX509KeyPair(serverCertFile, serverKeyFile)
|
certificate, err := tls.LoadX509KeyPair(serverCertFile, serverKeyFile)
|
||||||
@@ -608,14 +608,16 @@ 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 {
|
if creds == nil {
|
||||||
creds = insecure.NewCredentials()
|
creds = insecure.NewCredentials()
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if strings.HasPrefix(address, "xds:///") {
|
if strings.HasPrefix(address, "xds://") {
|
||||||
// The xds:/// prefix is used to signal to the gRPC client to use an xDS server to resolve the
|
// 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
|
// target. The relevant credentials will be automatically pulled from the GRPC_XDS_BOOTSTRAP or
|
||||||
// GRPC_XDS_BOOTSTRAP_CONFIG env vars.
|
// GRPC_XDS_BOOTSTRAP_CONFIG env vars.
|
||||||
creds, err = xdsCredentials.NewClientCredentials(xdsCredentials.ClientOptions{FallbackCreds: creds})
|
creds, err = xdsCredentials.NewClientCredentials(xdsCredentials.ClientOptions{FallbackCreds: creds})
|
||||||
@@ -629,7 +631,16 @@ func BlockingDial(ctx context.Context, network, address string, creds credential
|
|||||||
// 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.
|
||||||
result := make(chan interface{}, 1)
|
result := make(chan interface{}, 1)
|
||||||
|
|
||||||
|
// dialCompleted is closed once the outcome of the dial (ready connection
|
||||||
|
// or error) is known, so that connection teardown no longer needs to wait
|
||||||
|
// for a pending TLS alert to be read.
|
||||||
|
dialCompleted := make(chan struct{})
|
||||||
|
var dialCompletedOnce sync.Once
|
||||||
|
completeDial := func() { dialCompletedOnce.Do(func() { close(dialCompleted) }) }
|
||||||
|
defer completeDial()
|
||||||
|
|
||||||
writeResult := func(res interface{}) {
|
writeResult := func(res interface{}) {
|
||||||
|
completeDial()
|
||||||
// non-blocking write: we only need the first result
|
// non-blocking write: we only need the first result
|
||||||
select {
|
select {
|
||||||
case result <- res:
|
case result <- res:
|
||||||
@@ -642,43 +653,72 @@ func BlockingDial(ctx context.Context, network, address string, creds credential
|
|||||||
creds = &errSignalingCreds{
|
creds = &errSignalingCreds{
|
||||||
TransportCredentials: creds,
|
TransportCredentials: creds,
|
||||||
writeResult: writeResult,
|
writeResult: writeResult,
|
||||||
|
dialCompleted: dialCompleted,
|
||||||
}
|
}
|
||||||
|
|
||||||
dialer := func(ctx context.Context, address string) (net.Conn, error) {
|
switch network {
|
||||||
// NB: We *could* handle the TLS handshake ourselves, in the custom
|
case "":
|
||||||
// dialer (instead of customizing both the dialer and the credentials).
|
// no-op, use address as-is
|
||||||
// But that requires using insecure.NewCredentials() dial transport
|
case "tcp":
|
||||||
// option (so that the gRPC library doesn't *also* try to do a
|
if strings.HasPrefix(address, "unix://") {
|
||||||
// handshake). And that would mean that the library would send the
|
return nil, fmt.Errorf("tcp network type cannot use unix address %s", address)
|
||||||
// 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 conn, err
|
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) {
|
||||||
|
conn, err := (&net.Dialer{}).DialContext(ctx, network, address)
|
||||||
|
if err != nil {
|
||||||
|
// capture the error so we can provide a better message
|
||||||
|
writeResult(err)
|
||||||
|
}
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
opts = append([]grpc.DialOption{grpc.WithContextDialer(dialer)}, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Even with grpc.FailOnNonTempDialError, this call will usually timeout in
|
// grpc.NewClient does not connect immediately, so we use conn.Connect()
|
||||||
// the face of TLS handshake errors. So we can't rely on grpc.WithBlock() to
|
// to trigger eager connection and then poll connectivity state to block
|
||||||
// know when we're done. So we run it in a goroutine and then use result
|
// until ready. The errSignalingCreds wrapper will capture TLS handshake
|
||||||
// channel to either get the connection or fail-fast.
|
// errors and write them to the result channel for fail-fast behavior.
|
||||||
|
|
||||||
|
// Normalize address for NewClient which defaults to "dns" resolver.
|
||||||
|
// Bare host:port addresses need "passthrough:///" to preserve the old
|
||||||
|
// grpc.Dial behavior.
|
||||||
|
if !strings.Contains(address, "://") {
|
||||||
|
address = "passthrough:///" + address
|
||||||
|
}
|
||||||
|
|
||||||
|
opts = append(opts, grpc.WithTransportCredentials(creds))
|
||||||
|
|
||||||
|
conn, err := grpc.NewClient(address, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn.Connect()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
// We put grpc.FailOnNonTempDialError *before* the explicitly provided
|
for {
|
||||||
// options so that it could be overridden.
|
s := conn.GetState()
|
||||||
opts = append([]grpc.DialOption{grpc.FailOnNonTempDialError(true)}, opts...)
|
if s == connectivity.Ready {
|
||||||
// But we don't want caller to be able to override these two, so we put
|
writeResult(conn)
|
||||||
// them *after* the explicitly provided options.
|
return
|
||||||
opts = append(opts, grpc.WithBlock(), grpc.WithContextDialer(dialer), grpc.WithTransportCredentials(creds))
|
}
|
||||||
|
if s == connectivity.Shutdown {
|
||||||
conn, err := grpc.DialContext(ctx, address, opts...)
|
return
|
||||||
var res interface{}
|
}
|
||||||
if err != nil {
|
if !conn.WaitForStateChange(ctx, s) {
|
||||||
res = err
|
// Context expired
|
||||||
} else {
|
return
|
||||||
res = conn
|
}
|
||||||
}
|
}
|
||||||
writeResult(res)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@@ -696,13 +736,72 @@ func BlockingDial(ctx context.Context, network, address string, creds credential
|
|||||||
// it will use the writeResult function to notify on error.
|
// it will use the writeResult function to notify on error.
|
||||||
type errSignalingCreds struct {
|
type errSignalingCreds struct {
|
||||||
credentials.TransportCredentials
|
credentials.TransportCredentials
|
||||||
writeResult func(res interface{})
|
writeResult func(res interface{})
|
||||||
|
dialCompleted <-chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *errSignalingCreds) ClientHandshake(ctx context.Context, addr string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
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)
|
conn, auth, err := c.TransportCredentials.ClientHandshake(ctx, addr, rawConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.writeResult(err)
|
c.writeResult(err)
|
||||||
|
return conn, auth, err
|
||||||
}
|
}
|
||||||
return conn, auth, err
|
// Wrap the connection to capture post-handshake errors, e.g.:
|
||||||
|
// - TLS 1.3 client cert rejection (server sends alert after handshake)
|
||||||
|
// - Plaintext client to TLS server (server closes conn immediately)
|
||||||
|
return &errSignalingConn{Conn: conn, writeResult: c.writeResult, dialCompleted: c.dialCompleted}, auth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// maxTLSAlertWait is an upper bound on how long a failing dial's connection
|
||||||
|
// teardown waits for a pending TLS alert to be read. It is a heuristic: long
|
||||||
|
// enough for the reader goroutine to be scheduled even on a loaded machine,
|
||||||
|
// short enough to not noticeably delay a failing dial.
|
||||||
|
const maxTLSAlertWait = 50 * time.Millisecond
|
||||||
|
|
||||||
|
// errSignalingConn wraps a net.Conn to capture read errors and report
|
||||||
|
// them via writeResult. Close is delayed until the dial has completed to
|
||||||
|
// give the reader a chance to read a pending TLS alert before the
|
||||||
|
// connection is closed.
|
||||||
|
type errSignalingConn struct {
|
||||||
|
net.Conn
|
||||||
|
writeResult func(res interface{})
|
||||||
|
dialCompleted <-chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *errSignalingConn) Read(b []byte) (int, error) {
|
||||||
|
n, err := c.Conn.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
c.writeResult(err)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *errSignalingConn) Close() error {
|
||||||
|
// With TLS 1.3, the server may reject the connection *after* the
|
||||||
|
// handshake completes (e.g. a missing client cert), by sending an alert.
|
||||||
|
// If the transport closes the connection before that alert is read, the
|
||||||
|
// caller sees a less useful error (e.g. "use of closed network
|
||||||
|
// connection") instead of the TLS alert. So while the outcome of the
|
||||||
|
// dial is still undecided, give the reader a brief window to read a
|
||||||
|
// pending alert before closing the connection.
|
||||||
|
select {
|
||||||
|
case <-c.dialCompleted:
|
||||||
|
case <-time.After(maxTLSAlertWait):
|
||||||
|
}
|
||||||
|
return c.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsesXDS forwards the optional UsesXDS marker of the wrapped credentials. The
|
||||||
|
// xDS credentials returned for "xds://" targets implement this method, and
|
||||||
|
// grpc-go's cds balancer relies on a type assertion for it to decide whether to
|
||||||
|
// apply the security configuration (e.g. UpstreamTlsContext) delivered by the
|
||||||
|
// management server. Because errSignalingCreds embeds the TransportCredentials
|
||||||
|
// interface, that extra method is not promoted automatically, so we forward it
|
||||||
|
// explicitly. Without this, xDS-supplied mTLS is silently ignored and the
|
||||||
|
// connection falls back to the plain credentials.
|
||||||
|
func (c *errSignalingCreds) UsesXDS() bool {
|
||||||
|
if x, ok := c.TransportCredentials.(interface{ UsesXDS() bool }); ok {
|
||||||
|
return x.UsesXDS()
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-10
@@ -12,8 +12,8 @@ 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"
|
||||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import this because it appears in exported API
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/jhump/protoreflect/desc"
|
"github.com/jhump/protoreflect/desc"
|
||||||
"github.com/jhump/protoreflect/grpcreflect"
|
"github.com/jhump/protoreflect/grpcreflect"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@@ -74,10 +74,8 @@ func TestMain(m *testing.M) {
|
|||||||
defer svrReflect.Stop()
|
defer svrReflect.Stop()
|
||||||
|
|
||||||
// And a corresponding client
|
// And a corresponding client
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
if ccReflect, err = grpc.NewClient(fmt.Sprintf("passthrough:///127.0.0.1:%d", portReflect),
|
||||||
defer cancel()
|
grpc.WithTransportCredentials(insecure.NewCredentials())); err != nil {
|
||||||
if ccReflect, err = grpc.DialContext(ctx, fmt.Sprintf("127.0.0.1:%d", portReflect),
|
|
||||||
grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()); err != nil {
|
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer ccReflect.Close()
|
defer ccReflect.Close()
|
||||||
@@ -99,10 +97,8 @@ func TestMain(m *testing.M) {
|
|||||||
defer svrProtoset.Stop()
|
defer svrProtoset.Stop()
|
||||||
|
|
||||||
// And a corresponding client
|
// And a corresponding client
|
||||||
ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
|
if ccNoReflect, err = grpc.NewClient(fmt.Sprintf("passthrough:///127.0.0.1:%d", portProtoset),
|
||||||
defer cancel()
|
grpc.WithTransportCredentials(insecure.NewCredentials())); err != nil {
|
||||||
if ccNoReflect, err = grpc.DialContext(ctx, fmt.Sprintf("127.0.0.1:%d", portProtoset),
|
|
||||||
grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()); err != nil {
|
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer ccNoReflect.Close()
|
defer ccNoReflect.Close()
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ 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"
|
||||||
"github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import this because it appears in exported API
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/jhump/protoreflect/desc"
|
"github.com/jhump/protoreflect/desc"
|
||||||
"github.com/jhump/protoreflect/dynamic"
|
"github.com/jhump/protoreflect/dynamic"
|
||||||
"github.com/jhump/protoreflect/dynamic/grpcdynamic"
|
"github.com/jhump/protoreflect/dynamic/grpcdynamic"
|
||||||
@@ -106,7 +106,7 @@ func InvokeRPC(ctx context.Context, source DescriptorSource, ch grpcdynamic.Chan
|
|||||||
case isNotFoundError(err):
|
case isNotFoundError(err):
|
||||||
return fmt.Errorf("target server does not expose service %q", svc)
|
return fmt.Errorf("target server does not expose service %q", svc)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("failed to query for service descriptor %q: %v", svc, err)
|
return fmt.Errorf("failed to query for service descriptor %q: %w", svc, err)
|
||||||
}
|
}
|
||||||
sd, ok := dsc.(*desc.ServiceDescriptor)
|
sd, ok := dsc.(*desc.ServiceDescriptor)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -155,7 +155,7 @@ func invokeUnary(ctx context.Context, stub grpcdynamic.Stub, md *desc.MethodDesc
|
|||||||
|
|
||||||
err := requestData(req)
|
err := requestData(req)
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
return fmt.Errorf("error getting request data: %v", err)
|
return fmt.Errorf("error getting request data: %w", err)
|
||||||
}
|
}
|
||||||
if err != io.EOF {
|
if err != io.EOF {
|
||||||
// verify there is no second message, which is a usage error
|
// verify there is no second message, which is a usage error
|
||||||
@@ -163,7 +163,7 @@ func invokeUnary(ctx context.Context, stub grpcdynamic.Stub, md *desc.MethodDesc
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return fmt.Errorf("method %q is a unary RPC, but request data contained more than 1 message", md.GetFullyQualifiedName())
|
return fmt.Errorf("method %q is a unary RPC, but request data contained more than 1 message", md.GetFullyQualifiedName())
|
||||||
} else if err != io.EOF {
|
} else if err != io.EOF {
|
||||||
return fmt.Errorf("error getting request data: %v", err)
|
return fmt.Errorf("error getting request data: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +205,7 @@ func invokeClientStream(ctx context.Context, stub grpcdynamic.Stub, md *desc.Met
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error getting request data: %v", err)
|
return fmt.Errorf("error getting request data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = str.SendMsg(req)
|
err = str.SendMsg(req)
|
||||||
@@ -249,7 +249,7 @@ func invokeServerStream(ctx context.Context, stub grpcdynamic.Stub, md *desc.Met
|
|||||||
|
|
||||||
err := requestData(req)
|
err := requestData(req)
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
return fmt.Errorf("error getting request data: %v", err)
|
return fmt.Errorf("error getting request data: %w", err)
|
||||||
}
|
}
|
||||||
if err != io.EOF {
|
if err != io.EOF {
|
||||||
// verify there is no second message, which is a usage error
|
// verify there is no second message, which is a usage error
|
||||||
@@ -257,7 +257,7 @@ func invokeServerStream(ctx context.Context, stub grpcdynamic.Stub, md *desc.Met
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return fmt.Errorf("method %q is a server-streaming RPC, but request data contained more than 1 message", md.GetFullyQualifiedName())
|
return fmt.Errorf("method %q is a server-streaming RPC, but request data contained more than 1 message", md.GetFullyQualifiedName())
|
||||||
} else if err != io.EOF {
|
} else if err != io.EOF {
|
||||||
return fmt.Errorf("error getting request data: %v", err)
|
return fmt.Errorf("error getting request data: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,7 +326,7 @@ func invokeBidi(ctx context.Context, stub grpcdynamic.Stub, md *desc.MethodDescr
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("error getting request data: %v", err)
|
err = fmt.Errorf("error getting request data: %w", err)
|
||||||
cancel()
|
cancel()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-60
@@ -7,73 +7,27 @@ The release process consists of a handful of tasks:
|
|||||||
2. Build binaries for various platforms. This is done using the local `go` tool and uses `GOOS` and `GOARCH` environment variables to cross-compile for supported platforms.
|
2. Build binaries for various platforms. This is done using the local `go` tool and uses `GOOS` and `GOARCH` environment variables to cross-compile for supported platforms.
|
||||||
3. Creates a release in GitHub, uploads the binaries, and creates provisional release notes (in the form of a change log).
|
3. Creates a release in GitHub, uploads the binaries, and creates provisional release notes (in the form of a change log).
|
||||||
4. Build a docker image for the new release.
|
4. Build a docker image for the new release.
|
||||||
5. Push the docker image to Docker Hub, with both a version tag and the "latest" tag.
|
5. Push the docker image to `ghcr.io`, with both a version tag and the "latest" tag.
|
||||||
6. Submits a PR to update the [Homebrew](https://brew.sh/) recipe with the latest version.
|
|
||||||
|
|
||||||
Most of this is automated via a script in this same directory. The main thing you will need is a GitHub personal access token, which will be used for creating the release in GitHub (so you need write access to the fullstorydev/grpcurl repo) and to open a Homebrew pull request.
|
Most of this is automated via a script in this same directory. The main thing you will need is a GitHub personal access token, which will be used for creating the release in GitHub (so you need write access to the fullstorydev/grpcurl repo) and to open a Homebrew pull request.
|
||||||
|
|
||||||
## Creating a new release
|
## Creating a new release
|
||||||
|
|
||||||
So, to actually create a new release, just run the script in this directory.
|
Go to the [Release workflow](https://github.com/fullstorydev/grpcurl/actions/workflows/release.yml), click **Run workflow**, and provide:
|
||||||
|
|
||||||
First, you need a version number for the new release, following sem-ver format: `v<Major>.<Minor>.<Patch>`. Second, you need a personal access token for GitHub.
|
* **Branch**: the branch or commit to release from (usually `master`).
|
||||||
|
* **version**: the version number for the new release, in sem-ver format: `v<Major>.<Minor>.<Patch>`, e.g. `v2.3.4`.
|
||||||
|
* **dry_run**: check this to build everything but publish nothing. Useful for validating a change to the release tooling. The binaries are attached to the workflow run as an artifact.
|
||||||
|
|
||||||
We'll use `v2.3.4` as an example version and `abcdef0123456789abcdef` as an example GitHub token:
|
The workflow then:
|
||||||
|
1. Verifies the version is well-formed and that the tag does not already exist.
|
||||||
|
2. Runs the tests.
|
||||||
|
3. Creates and pushes the release tag.
|
||||||
|
4. Cross-compiles binaries for all supported platforms, creates the GitHub release with generated release notes, and uploads the archives. (This is goreleaser, driven by `.goreleaser.yml`.)
|
||||||
|
5. Builds a multi-arch (`linux/amd64`, `linux/arm64`) Docker image and pushes it to `ghcr.io`, tagged with both the version and `latest`.
|
||||||
|
|
||||||
```sh
|
## Release notes
|
||||||
# from the root of the repo
|
|
||||||
GITHUB_TOKEN=abcdef0123456789abcd \
|
|
||||||
./releasing/do-release.sh v2.3.4
|
|
||||||
```
|
|
||||||
|
|
||||||
Wasn't that easy! There is one last step: update the release notes in GitHub. By default, the script just records a change log of commit descriptions. Use that log (and, if necessary, drill into individual PRs included in the release) to flesh out notes in the format of the `RELEASE_NOTES.md` file _in this directory_. Then login to GitHub, go to the new release, edit the notes, and paste in the markdown you just wrote.
|
Release notes are generated, never hand-written. `changelog.use: github-native` in `.goreleaser.yml` hands the job to GitHub's own release-notes generator, which lists every PR merged since the previous tag with its title, link, and author, and credits new contributors.
|
||||||
|
|
||||||
That should be all there is to it! If things go wrong and you have to re-do part of the process, see the sections below.
|
`.github/release.yml` controls how those PRs are grouped. Labels are optional — an unlabeled PR lands in a catch-all "Changes" section — so this needs no ongoing upkeep. Two things make the notes read better, if you want them:
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
### GitHub Releases
|
|
||||||
The GitHub release is the first step performed by the `do-release.sh` script. So generally, if there is an issue with that step, you can re-try the whole script.
|
|
||||||
|
|
||||||
Note, if running the script did something wrong, you may have to first login to GitHub and remove uploaded artifacts for a botched release attempt. In general, this is _very undesirable_. Releases should usually be considered immutable. Instead of removing uploaded assets and providing new ones, it is often better to remove uploaded assets (to make bad binaries no longer available) and then _release a new patch version_. (You can edit the release notes for the botched version explaining why there are no artifacts for it.)
|
|
||||||
|
|
||||||
The steps to do a GitHub-only release (vs. running the entire script) are the following:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# from the root of the repo
|
|
||||||
git tag v2.3.4
|
|
||||||
GITHUB_TOKEN=abcdef0123456789abcdef \
|
|
||||||
GO111MODULE=on \
|
|
||||||
make release
|
|
||||||
```
|
|
||||||
|
|
||||||
The `git tag ...` step is necessary because the release target requires that the current SHA have a sem-ver tag. That's the version it will use when creating the release.
|
|
||||||
|
|
||||||
This will create the release in GitHub with provisional release notes that just include a change log of commit messages. You still need to login to GitHub and revise those notes to adhere to the recommended format. (See `RELEASE_NOTES.md` in this directory.)
|
|
||||||
|
|
||||||
### Docker Hub Releases
|
|
||||||
|
|
||||||
To re-run only the Docker Hub release steps, you can manually run through each step in the "Docker" section of `do_release.sh`.
|
|
||||||
|
|
||||||
If the `docker push ...` steps fail, you may need to run `docker login`, enter your Docker Hub login credentials, and then try to push again.
|
|
||||||
|
|
||||||
### Homebrew Releases
|
|
||||||
|
|
||||||
The last step is to update the Homebrew recipe to use the latest version. First, we need to compute the SHA256 checksum for the source archive:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# download the source archive from GitHub
|
|
||||||
URL=https://github.com/fullstorydev/grpcurl/archive/refs/tags/v2.3.4.tar.gz
|
|
||||||
curl -L -o tmp.tgz $URL
|
|
||||||
# and compute the SHA
|
|
||||||
SHA="$(sha256sum < tmp.tgz | awk '{ print $1 }')"
|
|
||||||
```
|
|
||||||
|
|
||||||
To actually create the brew PR, you need your GitHub personal access token again, as well as the URL and SHA from the previous step:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
HOMEBREW_GITHUB_API_TOKEN=abcdef0123456789abcdef \
|
|
||||||
brew bump-formula-pr --url $URL --sha256 $SHA grpcurl
|
|
||||||
```
|
|
||||||
|
|
||||||
This creates a PR to bump the formula to the new version. When this PR is merged by brew maintainers, the new version becomes available!
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
## Changes
|
|
||||||
|
|
||||||
### Command-line tool
|
|
||||||
|
|
||||||
* _In this list, describe the changes to the command-line tool._
|
|
||||||
* _Use one bullet per change. Include both bug-fixes and improvements. Omit this section if there are no changes that impact the command-line tool._
|
|
||||||
|
|
||||||
### Go package "github.com/fullstorydev/grpcurl"
|
|
||||||
|
|
||||||
* _In this list, describe the changes to exported API in the main package in this repo: "github.com/fullstorydev/grpcurl". These will often be closely related to changes to the command-line tool, though not always: changes that only impact the cmd/grpcurl directory of this repo do not impact exported API._
|
|
||||||
* _Use one bullet per change. Include both bug-fixes and improvements. Omit this section if there are no changes that impact the exported API._
|
|
||||||
+9
-11
@@ -37,8 +37,9 @@ $PREFIX git checkout go.mod go.sum
|
|||||||
|
|
||||||
# make sure credentials are valid for later push steps; this might
|
# make sure credentials are valid for later push steps; this might
|
||||||
# be interactive since this will prompt for username and password
|
# be interactive since this will prompt for username and password
|
||||||
# if there are no valid current credentials.
|
# if there are no valid current credentials. Use a GitHub personal access
|
||||||
$PREFIX docker login
|
# token with the write:packages scope as the password.
|
||||||
|
$PREFIX docker login ghcr.io
|
||||||
echo "$VERSION" > VERSION
|
echo "$VERSION" > VERSION
|
||||||
|
|
||||||
# Docker Buildx support is included in Docker 19.03
|
# Docker Buildx support is included in Docker 19.03
|
||||||
@@ -48,15 +49,12 @@ $PREFIX docker run --privileged --rm tonistiigi/binfmt:qemu-v6.1.0 --install all
|
|||||||
# Create a new builder instance
|
# Create a new builder instance
|
||||||
export DOCKER_CLI_EXPERIMENTAL=enabled
|
export DOCKER_CLI_EXPERIMENTAL=enabled
|
||||||
$PREFIX docker buildx create --use --name multiarch-builder --node multiarch-builder0
|
$PREFIX docker buildx create --use --name multiarch-builder --node multiarch-builder0
|
||||||
# push to docker hub, both the given version as a tag and for "latest" tag
|
# push to GHCR, both the given version as a tag and for "latest" tag
|
||||||
$PREFIX docker buildx build --platform linux/amd64,linux/s390x,linux/arm64,linux/ppc64le --tag fullstorydev/grpcurl:${VERSION} --tag fullstorydev/grpcurl:latest --push --progress plain --no-cache .
|
$PREFIX docker buildx build --platform linux/amd64,linux/s390x,linux/arm64,linux/ppc64le --tag ghcr.io/fullstorydev/grpcurl:${VERSION} --tag ghcr.io/fullstorydev/grpcurl:latest --push --progress plain --no-cache .
|
||||||
$PREFIX docker buildx build --platform linux/amd64,linux/s390x,linux/arm64,linux/ppc64le --tag fullstorydev/grpcurl:${VERSION}-alpine --tag fullstorydev/grpcurl:latest-alpine --push --progress plain --no-cache --target alpine .
|
$PREFIX docker buildx build --platform linux/amd64,linux/s390x,linux/arm64,linux/ppc64le --tag ghcr.io/fullstorydev/grpcurl:${VERSION}-alpine --tag ghcr.io/fullstorydev/grpcurl:latest-alpine --push --progress plain --no-cache --target alpine .
|
||||||
rm VERSION
|
rm VERSION
|
||||||
|
|
||||||
# Homebrew release
|
# Homebrew release
|
||||||
|
#
|
||||||
URL="https://github.com/fullstorydev/grpcurl/archive/refs/tags/${VERSION}.tar.gz"
|
# Nothing to do. Homebrew's tooling polls for new releases and opens the
|
||||||
curl -L -o tmp.tgz "$URL"
|
# homebrew-core bump PR on its own. See releasing/README.md.
|
||||||
SHA="$(sha256sum < tmp.tgz | awk '{ print $1 }')"
|
|
||||||
rm tmp.tgz
|
|
||||||
HOMEBREW_GITHUB_API_TOKEN="$GITHUB_TOKEN" $PREFIX brew bump-formula-pr --url "$URL" --sha256 "$SHA" grpcurl
|
|
||||||
|
|||||||
@@ -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
|
||||||
+88
-53
@@ -2,6 +2,7 @@ package grpcurl_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
|
"google.golang.org/grpc/peer"
|
||||||
|
|
||||||
. "github.com/fullstorydev/grpcurl"
|
. "github.com/fullstorydev/grpcurl"
|
||||||
grpcurl_testing "github.com/fullstorydev/grpcurl/internal/testing"
|
grpcurl_testing "github.com/fullstorydev/grpcurl/internal/testing"
|
||||||
@@ -101,64 +103,85 @@ func TestRequireClientCertTLS(t *testing.T) {
|
|||||||
simpleTest(t, e.cc)
|
simpleTest(t, e.cc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTLS12(t *testing.T) {
|
||||||
|
serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
tlsConf, err := ClientTLSConfig(false, "internal/testing/tls/ca.crt", "", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create client TLS config: %v", err)
|
||||||
|
}
|
||||||
|
tlsConf.MaxVersion = tls.VersionTLS12
|
||||||
|
|
||||||
|
e, err := createTestServerAndClient(serverCreds, credentials.NewTLS(tlsConf))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to setup server and client: %v", err)
|
||||||
|
}
|
||||||
|
defer e.Close()
|
||||||
|
|
||||||
|
tlsVersion := negotiatedTLSVersion(t, e.cc)
|
||||||
|
if tlsVersion != tls.VersionTLS12 {
|
||||||
|
t.Errorf("expected TLS 1.2, got 0x%04x", tlsVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTLS13(t *testing.T) {
|
||||||
|
serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
clientCreds, err := ClientTransportCredentials(false, "internal/testing/tls/ca.crt", "", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create client creds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := createTestServerAndClient(serverCreds, clientCreds)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to setup server and client: %v", err)
|
||||||
|
}
|
||||||
|
defer e.Close()
|
||||||
|
|
||||||
|
tlsVersion := negotiatedTLSVersion(t, e.cc)
|
||||||
|
if tlsVersion != tls.VersionTLS13 {
|
||||||
|
t.Errorf("expected TLS 1.3, got 0x%04x", tlsVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func negotiatedTLSVersion(t *testing.T, cc *grpc.ClientConn) uint16 {
|
||||||
|
t.Helper()
|
||||||
|
cl := grpcurl_testing.NewTestServiceClient(cc)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
var p peer.Peer
|
||||||
|
_, err := cl.UnaryCall(ctx, &grpcurl_testing.SimpleRequest{}, grpc.WaitForReady(true), grpc.Peer(&p))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("RPC failed: %v", err)
|
||||||
|
}
|
||||||
|
tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected TLS auth info, got %T", p.AuthInfo)
|
||||||
|
}
|
||||||
|
return tlsInfo.State.Version
|
||||||
|
}
|
||||||
|
|
||||||
func TestBrokenTLS_ClientPlainText(t *testing.T) {
|
func TestBrokenTLS_ClientPlainText(t *testing.T) {
|
||||||
serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", false)
|
serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create server creds: %v", err)
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// client connection (usually) succeeds since client is not waiting for TLS handshake
|
// Plaintext client to TLS server: the server expects a TLS handshake,
|
||||||
// (we try several times, but if we never get a connection and the error message is
|
// gets an HTTP/2 preface instead, and closes the connection.
|
||||||
// a known/expected possibility, we'll just bail)
|
e, err := createTestServerAndClient(serverCreds, nil)
|
||||||
var e testEnv
|
|
||||||
failCount := 0
|
|
||||||
for {
|
|
||||||
e, err = createTestServerAndClient(serverCreds, nil)
|
|
||||||
if err == nil {
|
|
||||||
// success!
|
|
||||||
defer e.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(err.Error(), "deadline exceeded") ||
|
|
||||||
strings.Contains(err.Error(), "use of closed network connection") {
|
|
||||||
// It is possible that the connection never becomes healthy:
|
|
||||||
// 1) grpc connects successfully
|
|
||||||
// 2) grpc client tries to send HTTP/2 preface and settings frame
|
|
||||||
// 3) server, expecting handshake, closes the connection
|
|
||||||
// 4) in the client, the write fails, so the connection never
|
|
||||||
// becomes ready
|
|
||||||
// The client will attempt to reconnect on transient errors, so
|
|
||||||
// may eventually bump into the connect time limit. This used to
|
|
||||||
// result in a "deadline exceeded" error, but more recent versions
|
|
||||||
// of the grpc library report any underlying I/O error instead, so
|
|
||||||
// we also check for "use of closed network connection".
|
|
||||||
failCount++
|
|
||||||
if failCount > 5 {
|
|
||||||
return // bail...
|
|
||||||
}
|
|
||||||
// we'll try again
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// some other error occurred, so we'll consider that a test failure
|
|
||||||
t.Fatalf("failed to setup server and client: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// but request fails because server closes connection upon seeing request
|
|
||||||
// bytes that are not a TLS handshake
|
|
||||||
cl := grpcurl_testing.NewTestServiceClient(e.cc)
|
|
||||||
_, err = cl.UnaryCall(context.Background(), &grpcurl_testing.SimpleRequest{})
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expecting failure")
|
e.Close()
|
||||||
|
t.Fatal("expecting failure when connecting plaintext to TLS server")
|
||||||
}
|
}
|
||||||
// various errors possible when server closes connection
|
if !strings.Contains(err.Error(), "EOF") &&
|
||||||
if !strings.Contains(err.Error(), "transport is closing") &&
|
|
||||||
!strings.Contains(err.Error(), "connection is unavailable") &&
|
|
||||||
!strings.Contains(err.Error(), "use of closed network connection") &&
|
!strings.Contains(err.Error(), "use of closed network connection") &&
|
||||||
!strings.Contains(err.Error(), "all SubConns are in TransientFailure") {
|
!strings.Contains(err.Error(), "connection reset by peer") {
|
||||||
|
t.Fatalf("expecting connection closed error, got: %v", err)
|
||||||
t.Fatalf("expecting transport failure, got: %v", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,8 +276,14 @@ 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") {
|
// The exact TLS alert varies by Go version and TLS version negotiated:
|
||||||
t.Fatalf("expecting TLS certificate error, got: %v", err)
|
// - TLS 1.2: "bad certificate" (Go <=1.24) or "handshake failure" (Go 1.25+)
|
||||||
|
// - TLS 1.3: "certificate required" (server rejects after handshake)
|
||||||
|
errMsg := err.Error()
|
||||||
|
if !strings.Contains(errMsg, "bad certificate") &&
|
||||||
|
!strings.Contains(errMsg, "handshake failure") &&
|
||||||
|
!strings.Contains(errMsg, "certificate required") {
|
||||||
|
t.Fatalf("expecting a TLS certificate error, got: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,8 +322,14 @@ 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") {
|
// The exact TLS alert varies by Go version and TLS version negotiated:
|
||||||
t.Fatalf("expecting TLS certificate error, got: %v", err)
|
// - TLS 1.2: "bad certificate" (Go <=1.24) or "handshake failure" (Go 1.25+)
|
||||||
|
// - TLS 1.3: "certificate required" (server rejects after handshake)
|
||||||
|
errMsg := err.Error()
|
||||||
|
if !strings.Contains(errMsg, "bad certificate") &&
|
||||||
|
!strings.Contains(errMsg, "handshake failure") &&
|
||||||
|
!strings.Contains(errMsg, "certificate required") {
|
||||||
|
t.Fatalf("expecting a TLS certificate error, got: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user