├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md ├── PULL_REQUEST_TEMPLATE.md ├── FUNDING.yml ├── renovate.json ├── CONTRIBUTING.md ├── dependabot.yml ├── workflows │ ├── semgrep.yml │ ├── pr.yml │ ├── release.yml │ └── go.yml ├── SUPPORT.md └── CODE_OF_CONDUCT.md ├── .prod ├── Makefile └── docker-compose.yml ├── .releaserc.js ├── cert ├── server.key └── server.crt ├── Makefile ├── AUTHORS ├── .gitignore ├── .gitattributes ├── go.mod ├── handler ├── addsvc │ ├── handler.go │ └── imported │ │ └── imported.go ├── hello │ └── handler.go └── grpcbin │ ├── handler_test.go │ └── handler.go ├── .golangci.yml ├── .assets ├── overview.dot └── overview.svg ├── LICENSE ├── Dockerfile ├── .goreleaser.yml ├── README.md ├── main.go ├── rules.mk └── go.sum /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @moul 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.prod/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | mkdir -p autocert 3 | docker-compose pull 4 | docker-compose up -d 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ["moul"] 2 | patreon: moul 3 | open_collective: moul 4 | custom: 5 | - "https://manfred.life/donate" 6 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "groupName": "all", 6 | "gomodTidy": true 7 | } 8 | -------------------------------------------------------------------------------- /.releaserc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | branch: 'master', 3 | plugins: [ 4 | '@semantic-release/commit-analyzer', 5 | '@semantic-release/release-notes-generator', 6 | '@semantic-release/github', 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, you can first discuss the change you wish to make via issue, 4 | email, or any other method with the maintainers of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | -------------------------------------------------------------------------------- /cert/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PARAMETERS----- 2 | BgUrgQQAIg== 3 | -----END EC PARAMETERS----- 4 | -----BEGIN EC PRIVATE KEY----- 5 | MIGkAgEBBDBtLICH6/Tv0fqSB73+nlnxiy2W38Ju8YdCqz5hx5cqx7CNTrbZLGBk 6 | vZPyBGuPbgOgBwYFK4EEACKhZANiAARuC1jRniuFmwHf+5rIzQarNpJfRIKldMc8 7 | 8JF2sgvf7YRmX/2SOYTHte0j52kSM5mV+JV/bKq4QUp7tJYTT+kdEMn7+ObGosOA 8 | UovaxbjUsJk6Hx4OcCI2Nbd1RGeCr5k= 9 | -----END EC PRIVATE KEY----- 10 | -------------------------------------------------------------------------------- /.prod/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.0' 2 | 3 | services: 4 | grpcbin: 5 | image: moul/grpcbin 6 | ports: 7 | - 80:80 # redirect to https:// 8 | - 9000:9000 # insecure gRPC 9 | - 9001:9001 # secure gRPC + secure webserver 10 | - 443:9001 # secure gRPC + secure webserver on default https port 11 | restart: unless-stopped 12 | command: --production 13 | volumes: 14 | - ./autocert:/root/autocert 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOPKG ?= moul.io/grpcbin 2 | DOCKER_IMAGE ?= moul/grpcbin 3 | GOBINS ?= . 4 | 5 | include rules.mk 6 | 7 | .PHONY: gentls 8 | gentls: 9 | mkdir -p cert 10 | openssl genrsa -out cert/server.key 2048 11 | openssl ecparam -genkey -name secp384r1 -out cert/server.key 12 | openssl req -new -x509 -sha256 -key cert/server.key -out cert/server.crt -days 3650 13 | 14 | .PHONY: doc 15 | doc: 16 | dot -Tsvg ./.assets/overview.dot > ./.assets/overview.svg 17 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This file lists all individuals having contributed content to the repository. 2 | # For how it is generated, see 'https://github.com/moul/rules.mk' 3 | 4 | dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 5 | Guilherme Salazar 6 | Manfred Touron <94029+moul@users.noreply.github.com> 7 | Manfred Touron 8 | moul-bot 9 | Pierre Wacrenier 10 | Renovate Bot 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | go-test.json 2 | 3 | # Temporary files 4 | *~ 5 | *# 6 | .#* 7 | coverage.txt 8 | 9 | go-build.log 10 | go-install.log 11 | go-test.json 12 | 13 | # Vendors 14 | package-lock.json 15 | node_modules/ 16 | vendor/ 17 | 18 | # Binaries for programs and plugins 19 | dist/ 20 | gin-bin 21 | *.exe 22 | *.exe~ 23 | *.dll 24 | *.so 25 | *.dylib 26 | 27 | # Test binary, build with `go test -c` 28 | *.test 29 | 30 | # Output of the go coverage tool, specifically when used with LiteIDE 31 | *.out 32 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: docker 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | - package-ecosystem: github-actions 10 | directory: "/" 11 | schedule: 12 | interval: daily 13 | time: "04:00" 14 | open-pull-requests-limit: 10 15 | - package-ecosystem: gomod 16 | directory: "/" 17 | schedule: 18 | interval: daily 19 | time: "04:00" 20 | open-pull-requests-limit: 10 21 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: {} 3 | push: 4 | branches: 5 | - master 6 | paths: 7 | - .github/workflows/semgrep.yml 8 | schedule: 9 | - cron: '0 0 * * 0' 10 | name: Semgrep 11 | jobs: 12 | semgrep: 13 | name: Scan 14 | runs-on: ubuntu-20.04 15 | env: 16 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 17 | container: 18 | image: returntocorp/semgrep 19 | steps: 20 | - uses: actions/checkout@v3 21 | - run: semgrep ci 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Collapse vendored and generated files on GitHub 5 | AUTHORS linguist-generated 6 | vendor/* linguist-vendored 7 | rules.mk linguist-vendored 8 | */vendor/* linguist-vendored 9 | *.gen.* linguist-generated 10 | *.pb.go linguist-generated 11 | *.pb.gw.go linguist-generated 12 | go.sum linguist-generated 13 | go.mod linguist-generated 14 | gen.sum linguist-generated 15 | 16 | # Reduce conflicts on markdown files 17 | *.md merge=union 18 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module moul.io/grpcbin 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 7 | github.com/moul/pb v0.0.0-20180404114147-54bdd96e6a52 8 | github.com/rogpeppe/fastuuid v1.1.0 // indirect 9 | github.com/smartystreets/goconvey v1.7.2 10 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a 11 | golang.org/x/net v0.0.0-20210610132358-84b48f89b13b 12 | google.golang.org/grpc v1.45.0 13 | ) 14 | 15 | replace github.com/grpc-ecosystem/grpc-gateway => github.com/moul/grpc-gateway v1.9.1-0.20190603230725-390f150e109c 16 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | 3 | on: 4 | pull_request: 5 | branches: [ master ] 6 | issue_comment: 7 | types: [ edited ] 8 | 9 | jobs: 10 | preview: 11 | name: Release-Notes Preview 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - run: | 16 | git fetch --prune --unshallow --tags 17 | - uses: snyk/release-notes-preview@v1.6.2 18 | with: 19 | releaseBranch: master 20 | env: 21 | GITHUB_PR_USERNAME: ${{ github.actor }} 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[IDEA] " 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /cert/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICDjCCAZUCCQDYqP2qN5CeqTAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJGUjEO 3 | MAwGA1UECAwFUGFyaXMxDjAMBgNVBAcMBVBhcmlzMRcwFQYDVQQKDA5NYW5mcmVk 4 | IFRvdXJvbjERMA8GA1UEAwwIZ3JwY2IuaW4xFjAUBgkqhkiG9w0BCQEWB21ANDIu 5 | YW0wHhcNMTcxMjE4MjAyNTE3WhcNMjcxMjE2MjAyNTE3WjBxMQswCQYDVQQGEwJG 6 | UjEOMAwGA1UECAwFUGFyaXMxDjAMBgNVBAcMBVBhcmlzMRcwFQYDVQQKDA5NYW5m 7 | cmVkIFRvdXJvbjERMA8GA1UEAwwIZ3JwY2IuaW4xFjAUBgkqhkiG9w0BCQEWB21A 8 | NDIuYW0wdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARuC1jRniuFmwHf+5rIzQarNpJf 9 | RIKldMc88JF2sgvf7YRmX/2SOYTHte0j52kSM5mV+JV/bKq4QUp7tJYTT+kdEMn7 10 | +ObGosOAUovaxbjUsJk6Hx4OcCI2Nbd1RGeCr5kwCgYIKoZIzj0EAwIDZwAwZAIw 11 | b1x4M7Y/wrHLp7Z8m4ujAkeZSXNY70+HOjZ7l6K5LvE+DgHAqkMTikFTQDIVtRUX 12 | AjAov5DGi0T6JnFRvGnNvYe7Q1aLpee3R4+DAyZTD0kRpP01/RQIFsI/lUMwxlbq 13 | eXk= 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /handler/addsvc/handler.go: -------------------------------------------------------------------------------- 1 | package addsvchandler 2 | 3 | import ( 4 | "golang.org/x/net/context" 5 | 6 | pb "github.com/moul/pb/addsvc/go-grpc" 7 | 8 | addservice "moul.io/grpcbin/handler/addsvc/imported" 9 | ) 10 | 11 | type Handler struct{} 12 | 13 | func (s Handler) Sum(ctx context.Context, in *pb.SumRequest) (*pb.SumReply, error) { 14 | v, err := addservice.New().Sum(ctx, int(in.A), int(in.B)) 15 | errStr := "" 16 | if err != nil { 17 | errStr = err.Error() 18 | } 19 | return &pb.SumReply{V: int64(v), Err: errStr}, nil 20 | } 21 | 22 | func (s Handler) Concat(ctx context.Context, in *pb.ConcatRequest) (*pb.ConcatReply, error) { 23 | v, err := addservice.New().Concat(ctx, in.A, in.B) 24 | errStr := "" 25 | if err != nil { 26 | errStr = err.Error() 27 | } 28 | return &pb.ConcatReply{V: v, Err: errStr}, nil 29 | } 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] " 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Type '....' 17 | 3. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots / Logs** 23 | If applicable, add screenshots or logs to help explain your problem. 24 | 25 | **Versions (please complete the following information, if relevant):** 26 | - Software version: [e.g. v1.2.3, latest, building from sources] 27 | - OS: [e.g. Ubuntu, Mac, iOS, ...] 28 | - Golang version [e.g. 1.13] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | deadline: 1m 3 | tests: false 4 | skip-files: 5 | - "testing.go" 6 | - ".*\\.pb\\.go" 7 | - ".*\\.gen\\.go" 8 | 9 | linters-settings: 10 | golint: 11 | min-confidence: 0 12 | maligned: 13 | suggest-new: true 14 | goconst: 15 | min-len: 5 16 | min-occurrences: 4 17 | misspell: 18 | locale: US 19 | 20 | linters: 21 | disable-all: true 22 | enable: 23 | - bodyclose 24 | - deadcode 25 | - depguard 26 | - dogsled 27 | - dupl 28 | - errcheck 29 | #- funlen 30 | - gochecknoinits 31 | #- gocognit 32 | - goconst 33 | - gocritic 34 | - gocyclo 35 | - gofmt 36 | - goimports 37 | - golint 38 | - gosimple 39 | - govet 40 | - ineffassign 41 | - interfacer 42 | - maligned 43 | - misspell 44 | - nakedret 45 | - prealloc 46 | - scopelint 47 | - staticcheck 48 | - structcheck 49 | #- stylecheck 50 | - typecheck 51 | - unconvert 52 | - unparam 53 | - unused 54 | - varcheck 55 | - whitespace 56 | -------------------------------------------------------------------------------- /.assets/overview.dot: -------------------------------------------------------------------------------- 1 | graph G { 2 | rankdir=LR; 3 | subgraph cluster_0 { 4 | style=filled; 5 | color=lightgrey; 6 | node[style=filled,color=white]; 7 | label="grpcb.in"; 8 | 9 | subgraph cluster_1 { 10 | label="listeners";style=filled;color=white; 11 | node[style=filled,color=lightgrey]; 12 | 9000[label="gRPC over HTTP\nport=9000"]; 13 | 443[label="https webserver +\ngRPC with TLS\nport=443 and port=9001"]; 14 | 80[label="http webserver that\nredirect to https"]; 15 | } 16 | 17 | subgraph cluster_2 { 18 | label="services";style=filled;color=white; 19 | node[style=filled,color=lightgrey]; 20 | hellosvc, addsvc, grpcbinsvc; 21 | } 22 | } 23 | grpcclient[label="gRPC client"]; 24 | 9000 -- hellosvc; 25 | 9000 -- addsvc; 26 | 443 -- hellosvc; 27 | 443 -- addsvc; 28 | 9000 -- grpcbinsvc; 29 | 443 -- grpcbinsvc; 30 | browser -- 443; 31 | browser -- 80; 32 | grpcclient -- 9000; 33 | grpcclient -- 443; 34 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2021 Manfred Touron 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | > This project has a [code of conduct](./CODE_OF_CONDUCT.md). 4 | > By interacting with this repository, organization, or community you agree to abide by its terms. 5 | 6 | Hi! :wave: We're excited that you're using this project and we’d love to help. To help us help you, please read through the following guidelines. 7 | 8 | Please understand that the people involved with this project often do so for fun, next to their day job; you are not entitled to free customer service. 9 | 10 | ## Questions 11 | 12 | Help us help you! 13 | 14 | Spending time framing a question and adding support links or resources makes it much easier for us to help. It’s easy to fall into the trap of asking something too specific when you’re close to a problem. Then, those trying to help you out have to spend a lot of time asking additional questions to understand what you are hoping to achieve. 15 | 16 | Spending the extra time up front can help save everyone time in the long run. 17 | 18 | * Try to define what you need help with: 19 | * Is there something in particular you want to do? 20 | * What problem are you encountering and what steps have you taken to try and fix it? 21 | * Is there a concept you’re not understanding? 22 | * Have you tried checking out the documentation? 23 | * Check out the tips on [requesting support](./CONTRIBUTING.md) in the contributing guide 24 | * The more time you put into asking your question, the better we can help you 25 | 26 | ## Contributions 27 | 28 | See [CONTRIBUTING.md](./CONTRIBUTING.md) on how to contribute. 29 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # dynamic config 2 | ARG BUILD_DATE 3 | ARG VCS_REF 4 | ARG VERSION 5 | 6 | # build 7 | FROM golang:1.18.1-alpine as builder 8 | RUN apk add --no-cache git gcc musl-dev make 9 | ENV GO111MODULE=on 10 | WORKDIR /go/src/moul.io/grpcbin 11 | COPY go.* ./ 12 | RUN go mod download 13 | COPY . ./ 14 | #RUN make install 15 | RUN go build -o /go/bin/grpcbin -ldflags "-extldflags \"-static\"" -v 16 | 17 | # minimalist runtime 18 | FROM alpine:3.15.1 19 | LABEL org.label-schema.build-date=$BUILD_DATE \ 20 | org.label-schema.name="grpcbin" \ 21 | org.label-schema.description="" \ 22 | org.label-schema.url="https://moul.io/grpcbin/" \ 23 | org.label-schema.vcs-ref=$VCS_REF \ 24 | org.label-schema.vcs-url="https://github.com/moul/grpcbin" \ 25 | org.label-schema.vendor="Manfred Touron" \ 26 | org.label-schema.version=$VERSION \ 27 | org.label-schema.schema-version="1.0" \ 28 | org.label-schema.cmd="docker run -i -t --rm moul/grpcbin" \ 29 | org.label-schema.help="docker exec -it $CONTAINER grpcbin --help" 30 | RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* 31 | COPY --from=builder /go/bin/grpcbin /bin/grpcbin 32 | COPY --from=builder /go/src/moul.io/grpcbin/cert /root/cert 33 | WORKDIR /root 34 | EXPOSE 9000 9001 80 35 | ENTRYPOINT ["/bin/grpcbin"] -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | env: 2 | - GO111MODULE=on 3 | - GOPROXY=https://proxy.golang.org 4 | before: 5 | hooks: 6 | - go mod download 7 | builds: 8 | - 9 | env: 10 | - CGO_ENABLED=0 11 | goos: 12 | - linux 13 | - darwin 14 | - windows 15 | goarch: 16 | - 386 17 | - amd64 18 | - arm 19 | - arm64 20 | ignore: 21 | - 22 | goos: darwin 23 | goarch: 386 24 | flags: 25 | - "-a" 26 | ldflags: 27 | - '-extldflags "-static"' 28 | checksum: 29 | name_template: '{{.ProjectName}}_checksums.txt' 30 | changelog: 31 | sort: asc 32 | filters: 33 | exclude: 34 | - '^docs:' 35 | - '^test:' 36 | - Merge pull request 37 | - Merge branch 38 | archives: 39 | - 40 | name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' 41 | replacements: 42 | darwin: Darwin 43 | linux: Linux 44 | windows: Windows 45 | 386: i386 46 | amd64: x86_64 47 | format_overrides: 48 | - 49 | goos: windows 50 | format: zip 51 | wrap_in_directory: true 52 | brews: 53 | - 54 | name: grpcbin 55 | # github: 56 | # owner: moul 57 | # name: homebrew-moul 58 | commit_author: 59 | name: moul-bot 60 | email: "bot@moul.io" 61 | homepage: https://github.com/moul/grpcbin 62 | description: "grpcbin" 63 | nfpms: 64 | - 65 | file_name_template: '{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' 66 | homepage: https://github.com/moul/grpcbin 67 | description: "grpcbin" 68 | maintainer: "Manfred Touron " 69 | license: "Apache-2.0 OR MIT" 70 | vendor: moul 71 | formats: 72 | - deb 73 | - rpm 74 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | release: 9 | name: releaser 10 | runs-on: ubuntu-latest 11 | steps: 12 | - 13 | name: Checkout 14 | uses: actions/checkout@v3 15 | - 16 | name: Unshallow 17 | run: git fetch --prune --unshallow 18 | - 19 | name: Run Semantic Release 20 | id: semantic 21 | uses: docker://ghcr.io/codfish/semantic-release-action:v1 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | - 25 | name: Set up Go 26 | if: steps.semantic.outputs.new-release-published == 'true' 27 | uses: actions/setup-go@v2 28 | with: 29 | go-version: 1.14 30 | - 31 | name: Cache Go modules 32 | if: steps.semantic.outputs.new-release-published == 'true' 33 | uses: actions/cache@v2.1.7 34 | with: 35 | path: ~/go/pkg/mod 36 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 37 | restore-keys: | 38 | ${{ runner.os }}-go- 39 | - 40 | name: Run GoReleaser 41 | if: steps.semantic.outputs.new-release-published == 'true' 42 | uses: goreleaser/goreleaser-action@v2.9.1 43 | with: 44 | version: latest 45 | args: release --rm-dist 46 | env: 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | - 49 | name: Register version on pkg.go.dev 50 | if: steps.semantic.outputs.new-release-published == 'true' 51 | run: | 52 | package=$(cat go.mod | grep ^module | awk '{print $2}') 53 | version=v${{ steps.semantic.outputs.release-version }} 54 | url=https://proxy.golang.org/${package}/@v/${version}.info 55 | set -x +e 56 | curl -i $url 57 | -------------------------------------------------------------------------------- /handler/hello/handler.go: -------------------------------------------------------------------------------- 1 | package hellohandler 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | 8 | "golang.org/x/net/context" 9 | 10 | pb "github.com/moul/pb/hello/go-grpc" 11 | ) 12 | 13 | type Handler struct{} 14 | 15 | const defaultGreeting = "noname" 16 | 17 | func (h *Handler) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) { 18 | greeting := defaultGreeting 19 | if in.Greeting != nil { 20 | greeting = *in.Greeting 21 | } 22 | reply := fmt.Sprintf("hello %s", greeting) 23 | return &pb.HelloResponse{Reply: &reply}, nil 24 | } 25 | 26 | func (h *Handler) LotsOfGreetings(stream pb.HelloService_LotsOfGreetingsServer) error { 27 | greetings := []string{} 28 | for { 29 | in, err := stream.Recv() 30 | if err == io.EOF { 31 | break 32 | } else if err != nil { 33 | return err 34 | } 35 | if in.Greeting != nil { 36 | greetings = append(greetings, *in.Greeting) 37 | } else { 38 | greetings = append(greetings, defaultGreeting) 39 | } 40 | } 41 | reply := fmt.Sprintf("hello %s", strings.Join(greetings, ", ")) 42 | return stream.SendAndClose(&pb.HelloResponse{Reply: &reply}) 43 | } 44 | 45 | func (h *Handler) LotsOfReplies(in *pb.HelloRequest, stream pb.HelloService_LotsOfRepliesServer) error { 46 | greeting := defaultGreeting 47 | if in.Greeting != nil { 48 | greeting = *in.Greeting 49 | } 50 | reply := fmt.Sprintf("hello %s", greeting) 51 | 52 | for i := 0; i < 10; i++ { 53 | if err := stream.Send(&pb.HelloResponse{Reply: &reply}); err != nil { 54 | return err 55 | } 56 | } 57 | return nil 58 | } 59 | 60 | func (h *Handler) BidiHello(stream pb.HelloService_BidiHelloServer) error { 61 | for { 62 | in, err := stream.Recv() 63 | if err == io.EOF { 64 | break 65 | } else if err != nil { 66 | return err 67 | } 68 | 69 | greeting := defaultGreeting 70 | if in.Greeting != nil { 71 | greeting = *in.Greeting 72 | } 73 | reply := fmt.Sprintf("hello %s", greeting) 74 | if err := stream.Send(&pb.HelloResponse{Reply: &reply}); err != nil { 75 | return err 76 | } 77 | } 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /handler/addsvc/imported/imported.go: -------------------------------------------------------------------------------- 1 | // imported and adapted from https://github.com/go-kit/kit/blob/master/examples/addsvc/pkg/addservice/service.go 2 | 3 | package addservice 4 | 5 | import ( 6 | "errors" 7 | 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | // Service describes a service that adds things together. 12 | type Service interface { 13 | Sum(ctx context.Context, a, b int) (int, error) 14 | Concat(ctx context.Context, a, b string) (string, error) 15 | } 16 | 17 | // New was changed 18 | // original signature was: func New(logger log.Logger, ints, chars metrics.Counter) Service { 19 | func New() Service { 20 | var svc Service 21 | { 22 | svc = NewBasicService() 23 | //svc = LoggingMiddleware(logger)(svc) 24 | //svc = InstrumentingMiddleware(ints, chars)(svc) 25 | } 26 | return svc 27 | } 28 | 29 | var ( 30 | // ErrTwoZeroes is an arbitrary business rule for the Add method. 31 | ErrTwoZeroes = errors.New("can't sum two zeroes") 32 | 33 | // ErrIntOverflow protects the Add method. We've decided that this error 34 | // indicates a misbehaving service and should count against e.g. circuit 35 | // breakers. So, we return it directly in endpoints, to illustrate the 36 | // difference. In a real service, this probably wouldn't be the case. 37 | ErrIntOverflow = errors.New("integer overflow") 38 | 39 | // ErrMaxSizeExceeded protects the Concat method. 40 | ErrMaxSizeExceeded = errors.New("result exceeds maximum size") 41 | ) 42 | 43 | // NewBasicService returns a naïve, stateless implementation of Service. 44 | func NewBasicService() Service { 45 | return basicService{} 46 | } 47 | 48 | type basicService struct{} 49 | 50 | const ( 51 | intMax = 1<<31 - 1 52 | intMin = -(intMax + 1) 53 | maxLen = 10 54 | ) 55 | 56 | func (s basicService) Sum(_ context.Context, a, b int) (int, error) { 57 | if a == 0 && b == 0 { 58 | return 0, ErrTwoZeroes 59 | } 60 | if (b > 0 && a > (intMax-b)) || (b < 0 && a < (intMin-b)) { 61 | return 0, ErrIntOverflow 62 | } 63 | return a + b, nil 64 | } 65 | 66 | // Concat implements Service. 67 | func (s basicService) Concat(_ context.Context, a, b string) (string, error) { 68 | if len(a)+len(b) > maxLen { 69 | return "", ErrMaxSizeExceeded 70 | } 71 | return a + b, nil 72 | } 73 | -------------------------------------------------------------------------------- /handler/grpcbin/handler_test.go: -------------------------------------------------------------------------------- 1 | package grpcbinhandler 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | . "github.com/smartystreets/goconvey/convey" 8 | 9 | pb "github.com/moul/pb/grpcbin/go-grpc" 10 | ) 11 | 12 | func TestHandler(t *testing.T) { 13 | Convey("Testing Handler{}", t, func() { 14 | s := Handler{} 15 | ctx := context.Background() 16 | 17 | Convey("Testing Index()", func() { 18 | resp, err := s.Index(ctx, &pb.EmptyMessage{}) 19 | So(err, ShouldBeNil) 20 | So(resp, ShouldNotBeNil) 21 | So(len(resp.Endpoints) > 0, ShouldBeTrue) 22 | So(len(resp.Endpoints[0].Path) > 0, ShouldBeTrue) 23 | }) 24 | 25 | Convey("Testing Empty()", func() { 26 | resp, err := s.Empty(ctx, &pb.EmptyMessage{}) 27 | So(err, ShouldBeNil) 28 | So(resp, ShouldNotBeNil) 29 | }) 30 | 31 | Convey("Testing DummyUnary()", func() { 32 | req := &pb.DummyMessage{ 33 | FString: "hello world", 34 | FInt32: 42, 35 | } 36 | resp, err := s.DummyUnary(ctx, req) 37 | So(err, ShouldBeNil) 38 | So(resp.FString, ShouldEqual, "hello world") 39 | So(resp.FInt32, ShouldEqual, 42) 40 | So(resp, ShouldResemble, req) 41 | }) 42 | 43 | Convey("Testing RandomError()", func() { 44 | _, err := s.RandomError(ctx, &pb.EmptyMessage{}) 45 | So(err, ShouldNotBeNil) 46 | }) 47 | 48 | Convey("Testing SpecificError()", func() { 49 | _, err := s.SpecificError(ctx, &pb.SpecificErrorRequest{ 50 | Code: 1, 51 | }) 52 | So(err, ShouldNotBeNil) 53 | So(err.Error(), ShouldEqual, "rpc error: code = Canceled desc = Canceled") 54 | 55 | _, err = s.SpecificError(ctx, &pb.SpecificErrorRequest{ 56 | Code: 2, 57 | }) 58 | So(err, ShouldNotBeNil) 59 | So(err.Error(), ShouldEqual, "rpc error: code = Unknown desc = Unknown") 60 | 61 | _, err = s.SpecificError(ctx, &pb.SpecificErrorRequest{ 62 | Code: 3, 63 | Reason: "lorem ipsum", 64 | }) 65 | So(err, ShouldNotBeNil) 66 | So(err.Error(), ShouldEqual, "rpc error: code = InvalidArgument desc = lorem ipsum") 67 | }) 68 | 69 | // Convey("Testing DummyBidirectionalStreamStream()", func() {}) 70 | // Convey("Testing DummyClientStream()", func() {}) 71 | // Convey("Testing DummyServerStream()", func() {}) 72 | // Convey("Testing HeadersUnary()", func() {}) 73 | // Convey("Testing NoResponseUnary()", func() {}) 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /handler/grpcbin/handler.go: -------------------------------------------------------------------------------- 1 | package grpcbinhandler 2 | 3 | import ( 4 | "io" 5 | "math/rand" 6 | "time" 7 | 8 | "golang.org/x/net/context" 9 | "google.golang.org/grpc/codes" 10 | "google.golang.org/grpc/metadata" 11 | "google.golang.org/grpc/status" 12 | 13 | pb "github.com/moul/pb/grpcbin/go-grpc" 14 | ) 15 | 16 | type Handler struct{} 17 | 18 | func (h *Handler) Index(ctx context.Context, in *pb.EmptyMessage) (*pb.IndexReply, error) { 19 | reply := pb.IndexReply{ 20 | Description: "gRPC testing server", 21 | Endpoints: []*pb.IndexReply_Endpoint{}, 22 | } 23 | for _, method := range pb.GRPCBin_serviceDesc.Methods { 24 | reply.Endpoints = append(reply.Endpoints, &pb.IndexReply_Endpoint{ 25 | Path: method.MethodName, 26 | // Description: FIXME get from comments 27 | }) 28 | } 29 | return &reply, nil 30 | } 31 | 32 | func (h *Handler) HeadersUnary(ctx context.Context, in *pb.EmptyMessage) (*pb.HeadersMessage, error) { 33 | md, ok := metadata.FromIncomingContext(ctx) 34 | if !ok { 35 | return nil, status.Error(codes.InvalidArgument, "cannot parse metadata from incoming context") 36 | } 37 | resp := pb.HeadersMessage{ 38 | Metadata: map[string]*pb.HeadersMessage_Values{}, 39 | } 40 | for key, values := range md { 41 | resp.Metadata[key] = &pb.HeadersMessage_Values{Values: values} 42 | } 43 | return &resp, nil 44 | } 45 | 46 | func (h *Handler) NoResponseUnary(ctx context.Context, in *pb.EmptyMessage) (*pb.EmptyMessage, error) { 47 | return nil, nil 48 | } 49 | 50 | func (h *Handler) Empty(ctx context.Context, in *pb.EmptyMessage) (*pb.EmptyMessage, error) { 51 | return &pb.EmptyMessage{}, nil 52 | } 53 | 54 | func (h *Handler) DummyUnary(ctx context.Context, in *pb.DummyMessage) (*pb.DummyMessage, error) { 55 | return in, nil 56 | } 57 | 58 | func (h *Handler) RandomError(ctx context.Context, in *pb.EmptyMessage) (*pb.EmptyMessage, error) { 59 | c := codes.Code(uint32(rand.Intn(16))) 60 | return &pb.EmptyMessage{}, status.Error(c, c.String()) 61 | } 62 | 63 | func (h *Handler) SpecificError(ctx context.Context, in *pb.SpecificErrorRequest) (*pb.EmptyMessage, error) { 64 | c := codes.Code(in.Code) 65 | msg := c.String() 66 | if in.Reason != "" { 67 | msg = in.Reason 68 | } 69 | return &pb.EmptyMessage{}, status.Error(c, msg) 70 | } 71 | 72 | func (h *Handler) DummyBidirectionalStreamStream(stream pb.GRPCBin_DummyBidirectionalStreamStreamServer) error { 73 | for { 74 | req, err := stream.Recv() 75 | if err == io.EOF { 76 | break 77 | } else if err != nil { 78 | return err 79 | } 80 | if err := stream.Send(req); err != nil { 81 | return err 82 | } 83 | } 84 | return nil 85 | } 86 | 87 | func (h *Handler) DummyClientStream(stream pb.GRPCBin_DummyClientStreamServer) error { 88 | var req *pb.DummyMessage 89 | var err error 90 | for i := 0; i < 10; i++ { 91 | req, err = stream.Recv() 92 | if err == io.EOF { 93 | break 94 | } else if err != nil { 95 | return err 96 | } 97 | } 98 | return stream.SendAndClose(req) 99 | } 100 | 101 | func (h *Handler) DummyServerStream(in *pb.DummyMessage, stream pb.GRPCBin_DummyServerStreamServer) error { 102 | for i := 0; i < 10; i++ { 103 | if err := stream.Send(in); err != nil { 104 | return err 105 | } 106 | time.Sleep(1 * time.Second) 107 | } 108 | return nil 109 | } 110 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at m+coc-report@42.am. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grpcbin 2 | httpbin like for gRPC 3 | 4 | [![CircleCI](https://circleci.com/gh/moul/grpcbin.svg?style=svg)](https://circleci.com/gh/moul/grpcbin) 5 | [![Docker Build Status](https://img.shields.io/docker/build/moul/grpcbin.svg)](https://hub.docker.com/r/moul/grpcbin/) 6 | [![Go Report Card](https://goreportcard.com/badge/moul.io/grpcbin)](https://goreportcard.com/report/moul.io/grpcbin) 7 | [![GoDoc](https://godoc.org/moul.io/grpcbin?status.svg)](https://godoc.org/moul.io/grpcbin/handler) 8 | [![License](https://img.shields.io/github/license/moul/grpcbin.svg)](https://github.com/moul/grpcbin/blob/master/LICENSE) 9 | 10 | ![overview](https://raw.githubusercontent.com/moul/grpcbin/master/.assets/overview.svg?sanitize=true) 11 | 12 | ## Links 13 | 14 | * Servers 15 | * insecure gRPC (over HTTP, without TLS): grpc://grpcb.in:9000 16 | * secure gRPC (with let's encrypt TLS): grpc://grpcb.in:443 and grpc://grpcb.in:9001 17 | * webserver: https://grpcb.in 18 | * Services 19 | * [grpcbin.proto](https://github.com/moul/pb/blob/master/grpcbin/grpcbin.proto) 20 | * [hello.proto](https://github.com/moul/pb/blob/master/hello/hello.proto) 21 | * [addsvc.proto](https://github.com/moul/pb/blob/master/addsvc/addsvc.proto) 22 | * Examples 23 | * multiple languages: https://github.com/moul/grpcbin-example 24 | * haskell: https://github.com/lucasdicioccio/http2-client-grpc-example/ 25 | 26 | ## Run server locally 27 | 28 | ```console 29 | $ docker run -it --rm -p 9000:9000 -p 9001:9001 moul/grpcbin 30 | 2017/12/18 14:48:01 listening on :9000 (insecure) 31 | 2017/12/18 14:48:01 listening on :9001 (secure) 32 | ``` 33 | 34 | ## Example 35 | 36 | See examples on a the dedicated repo: [grpcbin-example](https://github.com/moul/grpcbin-example) 37 | 38 | --- 39 | 40 | #### Golang 41 | 42 | ```go 43 | package main 44 | 45 | import ( 46 | "fmt" 47 | "log" 48 | 49 | "golang.org/x/net/context" 50 | "google.golang.org/grpc" 51 | 52 | pb "github.com/moul/pb/grpcbin/go-grpc" 53 | ) 54 | 55 | func main() { 56 | // dial 57 | conn, _ := grpc.Dial("grpcb.in:9000", grpc.WithInsecure()) 58 | defer conn.Close() 59 | 60 | // create client and context 61 | client := pb.NewGRPCBinClient(conn) 62 | ctx := context.Background() 63 | 64 | // call DummyUnary 65 | res, err := client.DummyUnary(ctx, &pb.DummyMessage{ 66 | FString: "hello", 67 | FInt32: 42, 68 | }) 69 | if err != nil { 70 | log.Fatalf("failed to call DummyUnary: %v", err) 71 | } 72 | fmt.Println(res) 73 | } 74 | ``` 75 | 76 | --- 77 | 78 | Example with [grpcc](https://github.com/njpatel/grpcc): 79 | 80 | ```console 81 | # fetch proto and install tool 82 | $ wget -qN https://github.com/moul/pb/raw/master/grpcbin/grpcbin.proto 83 | $ npm install -g grpcc 84 | 85 | # interactive client 86 | $ grpcc -i -p ./grpcbin.proto --address grpcb.in:9000 87 | Connecting to grpcbin.GRPCBin on grpcb.in:9000. Available globals: 88 | 89 | client - the client connection to GRPCBin 90 | index (EmptyMessage, callback) returns IndexReply 91 | dummyUnary (DummyMessage, callback) returns DummyMessage 92 | dummyServerStream (DummyMessage, callback) returns DummyMessage 93 | dummyClientStream (DummyMessage, callback) returns DummyMessage 94 | dummyBidirectionalStreamStream (DummyMessage, callback) returns DummyMessage 95 | 96 | printReply - function to easily print a unary call reply (alias: pr) 97 | streamReply - function to easily print stream call replies (alias: sr) 98 | createMetadata - convert JS objects into grpc metadata instances (alias: cm) 99 | 100 | GRPCBin@grpcb.in:9000> ^C 101 | 102 | # call index endpoint 103 | $ grpcc -i -p ./grpcbin.proto --address grpcb.in:9000 --eval 'client.index({}, printReply)' 104 | { 105 | "description": "gRPC testing server", 106 | "endpoints": [ 107 | { 108 | "path": "index", 109 | "description": "This endpoint." 110 | }, 111 | { 112 | "path": "dummyUnary", 113 | "description": "Unary endpoint that replies a received DummyMessage." 114 | }, 115 | [...] 116 | ] 117 | } 118 | 119 | # call dummyUnary with arguments 120 | $ grpcc -i -p ./grpcbin.proto --address grpcb.in:9000 --eval 'client.dummyUnary({f_string:"hello",f_int32:42}, printReply)' 121 | { 122 | "f_string": "hello", 123 | "f_strings": [], 124 | "f_int32": 42, 125 | "f_int32s": [], 126 | "f_enum": "ENUM_0", 127 | "f_enums": [], 128 | "f_sub": null, 129 | "f_subs": [], 130 | "f_bool": false, 131 | "f_bools": [], 132 | "f_int64": "0", 133 | "f_int64s": [], 134 | "f_bytes": { 135 | "type": "Buffer", 136 | "data": [] 137 | }, 138 | "f_bytess": [], 139 | "f_float": 0, 140 | "f_floats": [] 141 | } 142 | ``` 143 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | paths: 9 | - '**.go' 10 | - ".goreleaser.yml" 11 | - ".golangci.yml" 12 | - ".dockerignore" 13 | - "Makefile" 14 | - "rules.mk" 15 | - "go.*" 16 | - ".github/workflows/go.yml" 17 | pull_request: 18 | paths: 19 | - '**.go' 20 | - ".goreleaser.yml" 21 | - ".golangci.yml" 22 | - ".dockerignore" 23 | - "Makefile" 24 | - "rules.mk" 25 | - "go.*" 26 | - ".github/workflows/go.yml" 27 | 28 | jobs: 29 | docker-build: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v3 33 | - name: Build the Docker image 34 | run: docker build . --file Dockerfile 35 | goreleaser: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v3 40 | - name: Set up Go 41 | uses: actions/setup-go@v2 42 | with: 43 | go-version: 1.14 44 | - name: Cache Go modules 45 | uses: actions/cache@v2.1.7 46 | with: 47 | path: ~/go/pkg/mod 48 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 49 | restore-keys: | 50 | ${{ runner.os }}-go- 51 | - name: Run GoReleaser (Dry Run) 52 | uses: goreleaser/goreleaser-action@v2.9.1 53 | with: 54 | version: latest 55 | args: release --rm-dist --snapshot --skip-publish 56 | env: 57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | golangci-lint: 59 | runs-on: ubuntu-latest 60 | steps: 61 | - uses: actions/checkout@v3 62 | - name: golangci-lint 63 | uses: golangci/golangci-lint-action@v3.1.0 64 | with: 65 | version: v1.28 66 | github-token: ${{ secrets.GITHUB_TOKEN }} 67 | args: --timeout=2m 68 | only-new-issues: false 69 | working-directory: . 70 | tests-on-windows: 71 | needs: golangci-lint # run after golangci-lint action to not produce duplicated errors 72 | runs-on: windows-latest 73 | strategy: 74 | matrix: 75 | golang: 76 | #- 1.13 77 | - 1.14 78 | steps: 79 | - uses: actions/checkout@v3 80 | - name: Install Go 81 | uses: actions/setup-go@v2 82 | with: 83 | go-version: ${{ matrix.golang }} 84 | - name: Run tests on Windows 85 | run: make.exe unittest 86 | continue-on-error: true 87 | tests-on-mac: 88 | needs: golangci-lint # run after golangci-lint action to not produce duplicated errors 89 | runs-on: macos-latest 90 | strategy: 91 | matrix: 92 | golang: 93 | - 1.14 94 | env: 95 | OS: macos-latest 96 | GOLANG: ${{ matrix.golang }} 97 | steps: 98 | - uses: actions/checkout@v3 99 | - name: Install Go 100 | uses: actions/setup-go@v2 101 | with: 102 | go-version: ${{ matrix.golang }} 103 | - uses: actions/cache@v2.1.7 104 | with: 105 | path: ~/go/pkg/mod 106 | key: ${{ runner.os }}-go-${{ matrix.golang }}-${{ hashFiles('**/go.sum') }} 107 | restore-keys: | 108 | ${{ runner.os }}-go-${{ matrix.golang }}- 109 | - name: Compile the project 110 | run: make go.install 111 | - name: Run tests on Unix-like operating systems 112 | run: make unittest 113 | - name: Check go.mod and go.sum 114 | run: | 115 | go mod tidy -v 116 | git --no-pager diff go.mod go.sum 117 | git --no-pager diff --quiet go.mod go.sum 118 | - name: Upload coverage to Codecov 119 | uses: codecov/codecov-action@v2.1.0 120 | with: 121 | #token: ${{ secrets.CODECOV_TOKEN }} 122 | file: ./coverage.txt 123 | flags: unittests 124 | env_vars: OS,GOLANG 125 | name: codecov-umbrella 126 | fail_ci_if_error: false 127 | tests-on-linux: 128 | needs: golangci-lint # run after golangci-lint action to not produce duplicated errors 129 | runs-on: ubuntu-latest 130 | strategy: 131 | matrix: 132 | golang: 133 | #- 1.11 134 | #- 1.12 135 | - 1.13 136 | - 1.14 137 | env: 138 | OS: ubuntu-latest 139 | GOLANG: ${{ matrix.golang }} 140 | steps: 141 | - uses: actions/checkout@v3 142 | - name: Install Go 143 | uses: actions/setup-go@v2 144 | with: 145 | go-version: ${{ matrix.golang }} 146 | - uses: actions/cache@v2.1.7 147 | with: 148 | path: ~/go/pkg/mod 149 | key: ${{ runner.os }}-go-${{ matrix.golang }}-${{ hashFiles('**/go.sum') }} 150 | restore-keys: | 151 | ${{ runner.os }}-go-${{ matrix.golang }}- 152 | - name: Compile the project 153 | run: make go.install CI=true 154 | - name: Check go.mod and go.sum 155 | run: | 156 | go mod tidy -v 157 | git --no-pager diff go.mod go.sum 158 | git --no-pager diff --quiet go.mod go.sum 159 | - name: Run tests on Unix-like operating systems 160 | run: make unittest CI=true 161 | - name: Upload coverage to Codecov 162 | uses: codecov/codecov-action@v2.1.0 163 | with: 164 | #token: ${{ secrets.CODECOV_TOKEN }} 165 | file: ./coverage.txt 166 | flags: unittests 167 | env_vars: OS,GOLANG 168 | name: codecov-umbrella 169 | fail_ci_if_error: false 170 | - name: Upload to Report.ci 171 | if: always() && matrix.golang == 1.14 172 | env: 173 | REPORT_CI_TOKEN: ${{ secrets.REPORT_CI_TOKEN }} 174 | run: | 175 | ls -la go-install.log go-build.log go-test.json || true 176 | # merge build logs 177 | (cat go-install.log go-build.log > go-annotate.log) || true 178 | curl -s https://report.ci/annotate.py | python - --input="go-annotate.log" --tool=go || true 179 | curl -s https://report.ci/upload.py | python - --include="go-test.json" --framework=go || true 180 | -------------------------------------------------------------------------------- /.assets/overview.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | G 11 | 12 | 13 | cluster_0 14 | 15 | grpcb.in 16 | 17 | 18 | cluster_1 19 | 20 | listeners 21 | 22 | 23 | cluster_2 24 | 25 | services 26 | 27 | 28 | 29 | 9000 30 | 31 | gRPC over HTTP 32 | port=9000 33 | 34 | 35 | 36 | hellosvc 37 | 38 | hellosvc 39 | 40 | 41 | 42 | 9000--hellosvc 43 | 44 | 45 | 46 | 47 | addsvc 48 | 49 | addsvc 50 | 51 | 52 | 53 | 9000--addsvc 54 | 55 | 56 | 57 | 58 | grpcbinsvc 59 | 60 | grpcbinsvc 61 | 62 | 63 | 64 | 9000--grpcbinsvc 65 | 66 | 67 | 68 | 69 | 443 70 | 71 | https webserver + 72 | gRPC with TLS 73 | port=443 and port=9001 74 | 75 | 76 | 77 | 443--hellosvc 78 | 79 | 80 | 81 | 82 | 443--addsvc 83 | 84 | 85 | 86 | 87 | 443--grpcbinsvc 88 | 89 | 90 | 91 | 92 | 80 93 | 94 | http webserver that 95 | redirect to https 96 | 97 | 98 | 99 | grpcclient 100 | 101 | gRPC client 102 | 103 | 104 | 105 | grpcclient--9000 106 | 107 | 108 | 109 | 110 | grpcclient--443 111 | 112 | 113 | 114 | 115 | browser 116 | 117 | browser 118 | 119 | 120 | 121 | browser--443 122 | 123 | 124 | 125 | 126 | browser--80 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "flag" 6 | "fmt" 7 | "html/template" 8 | "log" 9 | "net" 10 | "net/http" 11 | "os" 12 | "os/signal" 13 | "strings" 14 | "syscall" 15 | 16 | "golang.org/x/crypto/acme/autocert" 17 | 18 | "google.golang.org/grpc" 19 | "google.golang.org/grpc/credentials" 20 | "google.golang.org/grpc/reflection" 21 | 22 | abepb "github.com/grpc-ecosystem/grpc-gateway/examples/proto/examplepb" 23 | addsvcpb "github.com/moul/pb/addsvc/go-grpc" 24 | grpcbinpb "github.com/moul/pb/grpcbin/go-grpc" 25 | hellopb "github.com/moul/pb/hello/go-grpc" 26 | 27 | abehandler "github.com/grpc-ecosystem/grpc-gateway/examples/server" 28 | addsvchandler "moul.io/grpcbin/handler/addsvc" 29 | grpcbinhandler "moul.io/grpcbin/handler/grpcbin" 30 | hellohandler "moul.io/grpcbin/handler/hello" 31 | ) 32 | 33 | var ( 34 | insecureAddr = flag.String("insecure-addr", ":9000", "The ip:port combination to listen on for insecure connections") 35 | secureAddr = flag.String("metrics-addr", ":9001", "The ip:port combination to listen on for secure connections") 36 | keyFile = flag.String("tls-key", "cert/server.key", "TLS private key file") 37 | certFile = flag.String("tls-cert", "cert/server.crt", "TLS cert file") 38 | inProduction = flag.Bool("production", false, "Production mode") 39 | productionHTTPAddr = flag.String("production-http-addr", ":80", "The ip:port combination to listen on for production HTTP server") 40 | autocertDir = flag.String("autocert-dir", "./autocert", "Autocert (let's encrypt) caching directory") 41 | ) 42 | 43 | var index = ` 44 | 45 | 46 |

grpcbin: gRPC Request & Response Service

47 |

Endpoints

48 | 52 |

Methods

53 |
    54 |
  • 55 | grpcbin.proto 56 |
      57 | {{- range .}} 58 |
    • {{.MethodName}}
    • 59 | {{- end}} 60 |
    61 |
  • 62 |
  • 63 | hello.proto 64 |
      65 |
    • SayHello
    • 66 |
    • LotsOfReplies
    • 67 |
    • LotsOfGreetings
    • 68 |
    • BidiHello
    • 69 |
    70 |
  • 71 |
  • 72 | addsvc.proto 73 |
      74 |
    • Sum
    • 75 |
    • Concat
    • 76 |
    77 |
  • 78 |
  • 79 | a_bit_of_everything.proto 80 |
      81 |
    • Create
    • 82 |
    • CreateBody
    • 83 |
    • Lookup
    • 84 |
    • Update
    • 85 |
    • Delete
    • 86 |
    • GetQuery
    • 87 |
    • Echo
    • 88 |
    • DeepPathEcho
    • 89 |
    • NoBindings
    • 90 |
    • Timeout
    • 91 |
    • ErrorWithDetails
    • 92 |
    • GetMessageWithBody
    • 93 |
    • PostWithEmptyBody
    • 94 |
    95 |
  • 96 |
97 |

Examples

98 | 102 |

About

103 | Developed by Manfred Touron, inspired by https://httpbin.org/ 104 | 105 | 106 | 107 | 108 | 109 | ` 110 | 111 | func main() { 112 | // parse flags 113 | flag.Parse() 114 | 115 | // insecure listener 116 | go func() { 117 | listener, err := net.Listen("tcp", *insecureAddr) 118 | if err != nil { 119 | log.Fatalf("failted to listen: %v", err) 120 | } 121 | 122 | // create gRPC server 123 | s := grpc.NewServer() 124 | grpcbinpb.RegisterGRPCBinServer(s, &grpcbinhandler.Handler{}) 125 | hellopb.RegisterHelloServiceServer(s, &hellohandler.Handler{}) 126 | addsvcpb.RegisterAddServer(s, &addsvchandler.Handler{}) 127 | abepb.RegisterABitOfEverythingServiceServer(s, abehandler.NewHandler()) 128 | // register reflection service on gRPC server 129 | reflection.Register(s) 130 | 131 | // serve 132 | log.Printf("listening on %s (insecure gRPC)\n", *insecureAddr) 133 | if err := s.Serve(listener); err != nil { 134 | log.Fatalf("failed to serve: %v", err) 135 | } 136 | }() 137 | 138 | // secure listener 139 | go func() { 140 | var ( 141 | creds credentials.TransportCredentials 142 | httpSrv = &http.Server{ 143 | Addr: *secureAddr, 144 | } 145 | ) 146 | 147 | // initialize tls configuration and grpc credentials based on production/development environment 148 | if *inProduction { 149 | m := autocert.Manager{ 150 | Prompt: autocert.AcceptTOS, 151 | HostPolicy: autocert.HostWhitelist("grpcb.in"), 152 | Cache: autocert.DirCache(*autocertDir), 153 | } 154 | httpSrv.TLSConfig = m.TLSConfig() 155 | creds = credentials.NewTLS(httpSrv.TLSConfig) 156 | } else { 157 | var err error 158 | creds, err = credentials.NewServerTLSFromFile(*certFile, *keyFile) 159 | if err != nil { 160 | log.Fatalf("failed to load TLS keys: %v", err) 161 | } 162 | cert, err := tls.LoadX509KeyPair(*certFile, *keyFile) 163 | if err != nil { 164 | log.Fatalf("failed to laod TLS keys: %v", err) 165 | } 166 | httpSrv.TLSConfig = &tls.Config{Certificates: []tls.Certificate{cert}} 167 | } 168 | 169 | // setup grpc servef 170 | s := grpc.NewServer(grpc.Creds(creds)) 171 | grpcbinpb.RegisterGRPCBinServer(s, &grpcbinhandler.Handler{}) 172 | hellopb.RegisterHelloServiceServer(s, &hellohandler.Handler{}) 173 | addsvcpb.RegisterAddServer(s, &addsvchandler.Handler{}) 174 | abepb.RegisterABitOfEverythingServiceServer(s, abehandler.NewHandler()) 175 | // register reflection service on gRPC server 176 | reflection.Register(s) 177 | 178 | // initilaize HTTP routing based on production/development environment 179 | if *inProduction { 180 | mux := http.NewServeMux() 181 | t := template.New("") 182 | var err error 183 | t, err = t.Parse(index) 184 | if err != nil { 185 | log.Fatalf("failt to parse template: %v", err) 186 | } 187 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 188 | if err2 := t.Execute(w, grpcbinpb.GRPCBin_serviceDesc.Methods); err != nil { 189 | http.Error(w, err2.Error(), http.StatusInternalServerError) 190 | } 191 | }) 192 | mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) { 193 | w.Header().Set("Content-Type", "image/x-icon") 194 | w.Header().Set("Cache-Control", "public, max-age=7776000") 195 | if _, err = fmt.Fprintln(w, "data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABmJLR0T///////8JWPfcAAAACXBIWXMAAABIAAAASABGyWs+AAAAF0lEQVRIx2NgGAWjYBSMglEwCkbBSAcACBAAAeaR9cIAAAAASUVORK5CYII="); err != nil { 196 | http.Error(w, err.Error(), http.StatusInternalServerError) 197 | } 198 | }) 199 | httpSrv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 200 | if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") { 201 | s.ServeHTTP(w, r) 202 | } else { 203 | mux.ServeHTTP(w, r) 204 | } 205 | }) 206 | } else { 207 | httpSrv.Handler = s 208 | } 209 | 210 | // listen and serve 211 | log.Printf("listening on %s (secure gRPC + secure HTTP/2)\n", *secureAddr) 212 | if err := httpSrv.ListenAndServeTLS("", ""); err != nil { 213 | log.Fatalf("failed to listen: %v", err) 214 | } 215 | }() 216 | 217 | if *inProduction { 218 | // production HTTP server (redirect to https) 219 | go func() { 220 | mux := http.NewServeMux() 221 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 222 | http.Redirect(w, r, "https://grpcb.in", 301) 223 | }) 224 | log.Printf("listening on %s (production HTTP)\n", *productionHTTPAddr) 225 | if err := http.ListenAndServe(*productionHTTPAddr, mux); err != nil { 226 | log.Fatalf("failed to listen: %v", err) 227 | } 228 | }() 229 | } 230 | 231 | // handle Ctrl+C 232 | c := make(chan os.Signal, 1) 233 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) 234 | log.Fatalf("%s", <-c) 235 | } 236 | -------------------------------------------------------------------------------- /rules.mk: -------------------------------------------------------------------------------- 1 | # +--------------------------------------------------------------+ 2 | # | * * * moul.io/rules.mk | 3 | # +--------------------------------------------------------------+ 4 | # | | 5 | # | ++ ______________________________________ | 6 | # | ++++ / \ | 7 | # | ++++ | | | 8 | # | ++++++++++ | https://moul.io/rules.mk is a set | | 9 | # | +++ | | of common Makefile rules that can | | 10 | # | ++ | | be configured from the Makefile | | 11 | # | + -== ==| | or with environment variables. | | 12 | # | ( <*> <*> | | | 13 | # | | | /| Manfred Touron | | 14 | # | | _) / | manfred.life | | 15 | # | | +++ / \______________________________________/ | 16 | # | \ =+ / | 17 | # | \ + | 18 | # | |\++++++ | 19 | # | | ++++ ||// | 20 | # | ___| |___ _||/__ __| 21 | # | / --- \ \| ||| __ _ ___ __ __/ /| 22 | # |/ | | \ \ / / ' \/ _ \/ // / / | 23 | # || | | | | | /_/_/_/\___/\_,_/_/ | 24 | # +--------------------------------------------------------------+ 25 | 26 | .PHONY: _default_entrypoint 27 | _default_entrypoint: help 28 | 29 | ## 30 | ## Common helpers 31 | ## 32 | 33 | rwildcard = $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d)) 34 | check-program = $(foreach exec,$(1),$(if $(shell PATH="$(PATH)" which $(exec)),,$(error "No $(exec) in PATH"))) 35 | my-filter-out = $(foreach v,$(2),$(if $(findstring $(1),$(v)),,$(v))) 36 | novendor = $(call my-filter-out,vendor/,$(1)) 37 | 38 | ## 39 | ## rules.mk 40 | ## 41 | ifneq ($(wildcard rules.mk),) 42 | .PHONY: rulesmk.bumpdeps 43 | rulesmk.bumpdeps: 44 | wget -O rules.mk https://raw.githubusercontent.com/moul/rules.mk/master/rules.mk 45 | BUMPDEPS_STEPS += rulesmk.bumpdeps 46 | endif 47 | 48 | ## 49 | ## Maintainer 50 | ## 51 | 52 | ifneq ($(wildcard .git/HEAD),) 53 | .PHONY: generate.authors 54 | generate.authors: AUTHORS 55 | AUTHORS: .git/ 56 | echo "# This file lists all individuals having contributed content to the repository." > AUTHORS 57 | echo "# For how it is generated, see 'https://github.com/moul/rules.mk'" >> AUTHORS 58 | echo >> AUTHORS 59 | git log --format='%aN <%aE>' | LC_ALL=C.UTF-8 sort -uf >> AUTHORS 60 | GENERATE_STEPS += generate.authors 61 | endif 62 | 63 | ## 64 | ## Golang 65 | ## 66 | 67 | ifndef GOPKG 68 | ifneq ($(wildcard go.mod),) 69 | GOPKG = $(shell sed '/module/!d;s/^omdule\ //' go.mod) 70 | endif 71 | endif 72 | ifdef GOPKG 73 | GO ?= go 74 | GOPATH ?= $(HOME)/go 75 | GO_INSTALL_OPTS ?= 76 | GO_TEST_OPTS ?= -test.timeout=30s 77 | GOMOD_DIRS ?= $(sort $(call novendor,$(dir $(call rwildcard,*,*/go.mod go.mod)))) 78 | GOCOVERAGE_FILE ?= ./coverage.txt 79 | GOTESTJSON_FILE ?= ./go-test.json 80 | GOBUILDLOG_FILE ?= ./go-build.log 81 | GOINSTALLLOG_FILE ?= ./go-install.log 82 | 83 | ifdef GOBINS 84 | .PHONY: go.install 85 | go.install: 86 | ifeq ($(CI),true) 87 | @rm -f /tmp/goinstall.log 88 | @set -e; for dir in $(GOBINS); do ( set -xe; \ 89 | cd $$dir; \ 90 | $(GO) install -v $(GO_INSTALL_OPTS) .; \ 91 | ); done 2>&1 | tee $(GOINSTALLLOG_FILE) 92 | 93 | else 94 | @set -e; for dir in $(GOBINS); do ( set -xe; \ 95 | cd $$dir; \ 96 | $(GO) install $(GO_INSTALL_OPTS) .; \ 97 | ); done 98 | endif 99 | INSTALL_STEPS += go.install 100 | 101 | .PHONY: go.release 102 | go.release: 103 | $(call check-program, goreleaser) 104 | goreleaser --snapshot --skip-publish --rm-dist 105 | @echo -n "Do you want to release? [y/N] " && read ans && \ 106 | if [ $${ans:-N} = y ]; then set -xe; goreleaser --rm-dist; fi 107 | RELEASE_STEPS += go.release 108 | endif 109 | 110 | .PHONY: go.unittest 111 | go.unittest: 112 | ifeq ($(CI),true) 113 | @echo "mode: atomic" > /tmp/gocoverage 114 | @rm -f $(GOTESTJSON_FILE) 115 | @set -e; for dir in $(GOMOD_DIRS); do (set -e; (set -euf pipefail; \ 116 | cd $$dir; \ 117 | (($(GO) test ./... $(GO_TEST_OPTS) -cover -coverprofile=/tmp/profile.out -covermode=atomic -race -json && touch $@.ok) | tee -a $(GOTESTJSON_FILE) 3>&1 1>&2 2>&3 | tee -a $(GOBUILDLOG_FILE); \ 118 | ); \ 119 | rm $@.ok 2>/dev/null || exit 1; \ 120 | if [ -f /tmp/profile.out ]; then \ 121 | cat /tmp/profile.out | sed "/mode: atomic/d" >> /tmp/gocoverage; \ 122 | rm -f /tmp/profile.out; \ 123 | fi)); done 124 | @mv /tmp/gocoverage $(GOCOVERAGE_FILE) 125 | else 126 | @echo "mode: atomic" > /tmp/gocoverage 127 | @set -e; for dir in $(GOMOD_DIRS); do (set -e; (set -xe; \ 128 | cd $$dir; \ 129 | $(GO) test ./... $(GO_TEST_OPTS) -cover -coverprofile=/tmp/profile.out -covermode=atomic -race); \ 130 | if [ -f /tmp/profile.out ]; then \ 131 | cat /tmp/profile.out | sed "/mode: atomic/d" >> /tmp/gocoverage; \ 132 | rm -f /tmp/profile.out; \ 133 | fi); done 134 | @mv /tmp/gocoverage $(GOCOVERAGE_FILE) 135 | endif 136 | 137 | .PHONY: go.checkdoc 138 | go.checkdoc: 139 | go doc $(first $(GOMOD_DIRS)) 140 | 141 | .PHONY: go.coverfunc 142 | go.coverfunc: go.unittest 143 | go tool cover -func=$(GOCOVERAGE_FILE) | grep -v .pb.go: | grep -v .pb.gw.go: 144 | 145 | .PHONY: go.lint 146 | go.lint: 147 | @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \ 148 | cd $$dir; \ 149 | golangci-lint run --verbose ./...; \ 150 | ); done 151 | 152 | .PHONY: go.tidy 153 | go.tidy: 154 | @# tidy dirs with go.mod files 155 | @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \ 156 | cd $$dir; \ 157 | $(GO) mod tidy; \ 158 | ); done 159 | 160 | .PHONY: go.depaware-update 161 | go.depaware-update: go.tidy 162 | @# gen depaware for bins 163 | @set -e; for dir in $(GOBINS); do ( set -xe; \ 164 | cd $$dir; \ 165 | $(GO) run github.com/tailscale/depaware --update .; \ 166 | ); done 167 | @# tidy unused depaware deps if not in a tools_test.go file 168 | @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \ 169 | cd $$dir; \ 170 | $(GO) mod tidy; \ 171 | ); done 172 | 173 | .PHONY: go.depaware-check 174 | go.depaware-check: go.tidy 175 | @# gen depaware for bins 176 | @set -e; for dir in $(GOBINS); do ( set -xe; \ 177 | cd $$dir; \ 178 | $(GO) run github.com/tailscale/depaware --check .; \ 179 | ); done 180 | 181 | 182 | .PHONY: go.build 183 | go.build: 184 | @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \ 185 | cd $$dir; \ 186 | $(GO) build ./...; \ 187 | ); done 188 | 189 | .PHONY: go.bump-deps 190 | go.bumpdeps: 191 | @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \ 192 | cd $$dir; \ 193 | $(GO) get -u ./...; \ 194 | ); done 195 | 196 | .PHONY: go.bump-deps 197 | go.fmt: 198 | @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \ 199 | cd $$dir; \ 200 | $(GO) run golang.org/x/tools/cmd/goimports -w `go list -f '{{.Dir}}' ./...` \ 201 | ); done 202 | 203 | VERIFY_STEPS += go.depaware-check 204 | BUILD_STEPS += go.build 205 | BUMPDEPS_STEPS += go.bumpdeps go.depaware-update 206 | TIDY_STEPS += go.tidy 207 | LINT_STEPS += go.lint 208 | UNITTEST_STEPS += go.unittest 209 | FMT_STEPS += go.fmt 210 | 211 | # FIXME: disabled, because currently slow 212 | # new rule that is manually run sometimes, i.e. `make pre-release` or `make maintenance`. 213 | # alternative: run it each time the go.mod is changed 214 | #GENERATE_STEPS += go.depaware-update 215 | endif 216 | 217 | ## 218 | ## Gitattributes 219 | ## 220 | 221 | ifneq ($(wildcard .gitattributes),) 222 | .PHONY: _linguist-ignored 223 | _linguist-kept: 224 | @git check-attr linguist-vendored $(shell git check-attr linguist-generated $(shell find . -type f | grep -v .git/) | grep unspecified | cut -d: -f1) | grep unspecified | cut -d: -f1 | sort 225 | 226 | .PHONY: _linguist-kept 227 | _linguist-ignored: 228 | @git check-attr linguist-vendored linguist-ignored `find . -not -path './.git/*' -type f` | grep '\ set$$' | cut -d: -f1 | sort -u 229 | endif 230 | 231 | ## 232 | ## Node 233 | ## 234 | 235 | ifndef NPM_PACKAGES 236 | ifneq ($(wildcard package.json),) 237 | NPM_PACKAGES = . 238 | endif 239 | endif 240 | ifdef NPM_PACKAGES 241 | .PHONY: npm.publish 242 | npm.publish: 243 | @echo -n "Do you want to npm publish? [y/N] " && read ans && \ 244 | @if [ $${ans:-N} = y ]; then \ 245 | set -e; for dir in $(NPM_PACKAGES); do ( set -xe; \ 246 | cd $$dir; \ 247 | npm publish --access=public; \ 248 | ); done; \ 249 | fi 250 | RELEASE_STEPS += npm.publish 251 | endif 252 | 253 | ## 254 | ## Docker 255 | ## 256 | 257 | docker_build = docker build \ 258 | --build-arg VCS_REF=`git rev-parse --short HEAD` \ 259 | --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \ 260 | --build-arg VERSION=`git describe --tags --always` \ 261 | -t "$2" -f "$1" "$(dir $1)" 262 | 263 | ifndef DOCKERFILE_PATH 264 | DOCKERFILE_PATH = ./Dockerfile 265 | endif 266 | ifndef DOCKER_IMAGE 267 | ifneq ($(wildcard Dockerfile),) 268 | DOCKER_IMAGE = $(notdir $(PWD)) 269 | endif 270 | endif 271 | ifdef DOCKER_IMAGE 272 | ifneq ($(DOCKER_IMAGE),none) 273 | .PHONY: docker.build 274 | docker.build: 275 | $(call check-program, docker) 276 | $(call docker_build,$(DOCKERFILE_PATH),$(DOCKER_IMAGE)) 277 | 278 | BUILD_STEPS += docker.build 279 | endif 280 | endif 281 | 282 | ## 283 | ## Common 284 | ## 285 | 286 | TEST_STEPS += $(UNITTEST_STEPS) 287 | TEST_STEPS += $(LINT_STEPS) 288 | TEST_STEPS += $(TIDY_STEPS) 289 | 290 | ifneq ($(strip $(TEST_STEPS)),) 291 | .PHONY: test 292 | test: $(PRE_TEST_STEPS) $(TEST_STEPS) 293 | endif 294 | 295 | ifdef INSTALL_STEPS 296 | .PHONY: install 297 | install: $(PRE_INSTALL_STEPS) $(INSTALL_STEPS) 298 | endif 299 | 300 | ifdef UNITTEST_STEPS 301 | .PHONY: unittest 302 | unittest: $(PRE_UNITTEST_STEPS) $(UNITTEST_STEPS) 303 | endif 304 | 305 | ifdef LINT_STEPS 306 | .PHONY: lint 307 | lint: $(PRE_LINT_STEPS) $(FMT_STEPS) $(LINT_STEPS) 308 | endif 309 | 310 | ifdef TIDY_STEPS 311 | .PHONY: tidy 312 | tidy: $(PRE_TIDY_STEPS) $(TIDY_STEPS) 313 | endif 314 | 315 | ifdef BUILD_STEPS 316 | .PHONY: build 317 | build: $(PRE_BUILD_STEPS) $(BUILD_STEPS) 318 | endif 319 | 320 | ifdef VERIFY_STEPS 321 | .PHONY: verify 322 | verify: $(PRE_VERIFY_STEPS) $(VERIFY_STEPS) 323 | endif 324 | 325 | ifdef RELEASE_STEPS 326 | .PHONY: release 327 | release: $(PRE_RELEASE_STEPS) $(RELEASE_STEPS) 328 | endif 329 | 330 | ifdef BUMPDEPS_STEPS 331 | .PHONY: bumpdeps 332 | bumpdeps: $(PRE_BUMDEPS_STEPS) $(BUMPDEPS_STEPS) 333 | endif 334 | 335 | ifdef FMT_STEPS 336 | .PHONY: fmt 337 | fmt: $(PRE_FMT_STEPS) $(FMT_STEPS) 338 | endif 339 | 340 | ifdef GENERATE_STEPS 341 | .PHONY: generate 342 | generate: $(PRE_GENERATE_STEPS) $(GENERATE_STEPS) 343 | endif 344 | 345 | .PHONY: help 346 | help:: 347 | @echo "General commands:" 348 | @[ "$(BUILD_STEPS)" != "" ] && echo " build" || true 349 | @[ "$(BUMPDEPS_STEPS)" != "" ] && echo " bumpdeps" || true 350 | @[ "$(FMT_STEPS)" != "" ] && echo " fmt" || true 351 | @[ "$(GENERATE_STEPS)" != "" ] && echo " generate" || true 352 | @[ "$(INSTALL_STEPS)" != "" ] && echo " install" || true 353 | @[ "$(LINT_STEPS)" != "" ] && echo " lint" || true 354 | @[ "$(RELEASE_STEPS)" != "" ] && echo " release" || true 355 | @[ "$(TEST_STEPS)" != "" ] && echo " test" || true 356 | @[ "$(TIDY_STEPS)" != "" ] && echo " tidy" || true 357 | @[ "$(UNITTEST_STEPS)" != "" ] && echo " unittest" || true 358 | @[ "$(VERIFY_STEPS)" != "" ] && echo " verify" || true 359 | @# FIXME: list other commands 360 | 361 | print-% : ; $(info $* is a $(flavor $*) variable set to [$($*)]) @true 362 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 5 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 6 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 7 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 8 | github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= 9 | github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 10 | github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 11 | github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 14 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 15 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 16 | github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= 17 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 18 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 19 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 20 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 21 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 22 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 23 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 24 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 25 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 26 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 27 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 28 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 29 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 30 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 31 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 32 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 33 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 34 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 35 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 36 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 37 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 38 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 39 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 40 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 41 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 42 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 43 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 44 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 45 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 46 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 47 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 48 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 49 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 50 | github.com/moul/grpc-gateway v1.9.1-0.20190603230725-390f150e109c h1:WyxpRryjfhBIWP3nNoI5iGzxEChh9N3u+5OReTa0bxo= 51 | github.com/moul/grpc-gateway v1.9.1-0.20190603230725-390f150e109c/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 52 | github.com/moul/pb v0.0.0-20180404114147-54bdd96e6a52 h1:8zDEa5yAIWYBHSDpPbSgGIBL/SvPSE9/FlB3aQ54d/A= 53 | github.com/moul/pb v0.0.0-20180404114147-54bdd96e6a52/go.mod h1:jE2HT8eoucYyUPBFJMreiVlC3KPHkDMtN8wn+ef7Y64= 54 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 55 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 56 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 57 | github.com/rogpeppe/fastuuid v1.1.0 h1:INyGLmTCMGFr6OVIb977ghJvABML2CMVjPoRfNDdYDo= 58 | github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 59 | github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= 60 | github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= 61 | github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= 62 | github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= 63 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 64 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 65 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 66 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 67 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 68 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 69 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= 70 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= 71 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 72 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 73 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 74 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 75 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 76 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 77 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 78 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 79 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 80 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 81 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 82 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 83 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 84 | golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A= 85 | golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 86 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 87 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 88 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 89 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 90 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 91 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 92 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 93 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 94 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 95 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 96 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 97 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 98 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= 99 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 100 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 101 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 102 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 103 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 104 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 105 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 106 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 107 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 108 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 109 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 110 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 111 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 112 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 113 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 114 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 115 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 116 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 117 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= 118 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 119 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 120 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 121 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 122 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 123 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 124 | google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= 125 | google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= 126 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 127 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 128 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 129 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 130 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 131 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 132 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 133 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 134 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 135 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 136 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= 137 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 138 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 139 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 140 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 141 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 142 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 143 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 144 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 145 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 146 | --------------------------------------------------------------------------------