diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..c0712ac --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ + * @odsod @ericwenn diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..0e0b0a1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 + +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: daily + + - package-ecosystem: gomod + directory: / + schedule: + interval: daily diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7e461b0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,60 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + branches: + - "*" + +jobs: + make: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2.3.4 + + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: ^1.16 + + - name: Setup Node + uses: actions/setup-node@v2.4.0 + with: + node-version: 12 + + - name: Make + run: make + + release: + needs: [make] + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2.3.4 + + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: ^1.16 + + - name: Setup Node + uses: actions/setup-node@v2.4.0 + with: + node-version: 12 + + - name: Run semantic-release + run: make semantic-release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Run goreleaser + uses: goreleaser/goreleaser-action@v2 + with: + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f5b0fed --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +tools/*/*/ +node_modules/ +build/ diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..005647c --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,32 @@ +before: + hooks: + - go mod download + +builds: + - id: protoc-gen-go-grpc-service-config + binary: protoc-gen-go-grpc-service-config + main: ./main.go + env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + +archives: + - replacements: + darwin: Darwin + linux: Linux + windows: Windows + 386: i386 + amd64: x86_64 + +checksum: + name_template: "checksums.txt" + +snapshot: + name_template: "{{ .Tag }}-next" + +release: + github: + prerelease: auto diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..a899c5e --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,129 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. + +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7d64276 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright 2021 Einride AB + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..42f8183 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +SHELL := /bin/bash + +all: \ + commitlint \ + prettier-markdown \ + go-lint \ + go-review \ + go-test \ + go-mod-tidy \ + git-verify-nodiff + +include tools/commitlint/rules.mk +include tools/git-verify-nodiff/rules.mk +include tools/golangci-lint/rules.mk +include tools/goreview/rules.mk +include tools/prettier/rules.mk +include tools/semantic-release/rules.mk + +.PHONY: clean +clean: + $(info [$@] removing generated files...) + @rm -rf build + @find . -type f -name '*_gen.go' -exec rm {} \+ + +.PHONY: go-mod-tidy +go-mod-tidy: + $(info [$@] tidying Go module files...) + @go mod tidy -v + +.PHONY: go-test +go-test: + $(info [$@] running Go tests...) + @mkdir -p build/coverage + @go test -short -race -coverprofile=build/coverage/$@.txt -covermode=atomic ./... diff --git a/README.md b/README.md new file mode 100644 index 0000000..d073c7f --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +# protoc-gen-go-grpc-service-config + +[gRPC service config][grpc-service-config] without service discovery, by bundling the service +config with the generated code. + +[grpc-service-config]: https://github.com/grpc/grpc/blob/master/doc/service_config.md + +# How to + +## Step 1: Add a service config JSON file to your package + +There is assumed to be one JSON file per package, named according to: +`_grpc_service_config.json`. + +For example `src/example/v1/example_grpc_service_config.json`: + +```json +{ + "methodConfig": [ + { + "name": [{ "service": "example.v1.ExampleService" }], + "timeout": "10s", + "retryPolicy": { + "initialBackoff": "0.200s", + "maxBackoff": "60s", + "maxAttempts": 5, + "backoffMultiplier": 1.3, + "retryableStatusCodes": ["UNAVAILABLE"] + } + } + ] +} +``` + +## Step 2: Run the protoc plugin + +Use the required `path` option to tell the generator where to load JSON +files from. + +Use the optional `validate` option to validate that every package has a +service config JSON, and that the format is valid. + +```bash +protoc + -I src \ + --go_out=gen/go \ + --go-grpc_out=gen/go \ + --go-grpc-service-config_out=gen/go \ + --go-grpc-service-config_opt=path=src \ + --go-grpc-service-config_opt=validate=true +``` + +Your generated code output will now have a Go file corresponding to every +service config JSON file. + +For example `gen/go/example/v1/example_grpc_service_config.json.go`: + +```go +package examplev1 + +// ServiceConfig is the service config for all services in the package. +// Source: example_grpc_service_config.json. +const ServiceConfig = `{ + "methodConfig": [ + { + "name": [{ "service": "example.v1.ExampleService" }], + "timeout": "10s", + "retryPolicy": { + "initialBackoff": "0.200s", + "maxBackoff": "60s", + "maxAttempts": 5, + "backoffMultiplier": 1.3, + "retryableStatusCodes": ["UNAVAILABLE"] + } + } + ] +} +` +``` + +## Step 3: Use your bundled service config when dialing + +```go +conn, err := grpc.DialContext( + ctx, + "example.com:443", + grpc.WithDefaultServiceConfig(examplev1.ServiceConfig), +) +``` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ee46b59 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module go.einride.tech/protoc-gen-go-grpc-service-config + +go 1.16 + +require ( + google.golang.org/grpc v1.39.1 + google.golang.org/protobuf v1.27.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5dc4098 --- /dev/null +++ b/go.sum @@ -0,0 +1,113 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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/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-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.39.1 h1:f37vZbBVTiJ6jKG5mWz8ySOBxNqy6ViPgyhSdVnxF3E= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go new file mode 100644 index 0000000..8bc6cc2 --- /dev/null +++ b/main.go @@ -0,0 +1,183 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "net" + "os" + "path/filepath" + "sync" + + "google.golang.org/grpc" + "google.golang.org/protobuf/compiler/protogen" +) + +const docURL = "https://github.com/grpc/grpc/blob/master/doc/service_config.md" + +func main() { + var ( + flags flag.FlagSet + path = flags.String("path", "", "input path of service config JSON files") + validate = flags.Bool("validate", false, "validate service configs") + required = flags.Bool("required", false, "require every service to have a service config") + ) + protogen.Options{ + ParamFunc: flags.Set, + }.Run(func(gen *protogen.Plugin) error { + p := plugin{ + gen: gen, + path: *path, + } + if *validate { + if err := p.validate(*required); err != nil { + return err + } + } + return p.run() + }) +} + +type plugin struct { + gen *protogen.Plugin + path string +} + +func (p *plugin) run() error { + generatedServiceConfigFiles := map[string]struct{}{} + for _, file := range p.gen.Files { + if !file.Generate { + continue + } + for _, service := range file.Services { + serviceConfigFile := p.resolveServiceConfigFile(service) + if _, err := os.Stat(serviceConfigFile); err != nil { + continue + } + if _, ok := generatedServiceConfigFiles[serviceConfigFile]; ok { + continue + } + generatedServiceConfigFiles[serviceConfigFile] = struct{}{} + data, err := ioutil.ReadFile(serviceConfigFile) + if err != nil { + return err + } + if err := json.Unmarshal(data, &serviceConfig{}); err != nil { + return fmt.Errorf("run: invalid service config file %s: %w", serviceConfigFile, err) + } + g := p.gen.NewGeneratedFile( + filepath.Dir(file.GeneratedFilenamePrefix)+"/"+filepath.Base(serviceConfigFile)+".go", + file.GoImportPath, + ) + g.P("package ", file.GoPackageName) + g.P() + g.P("// ServiceConfig is the service config for all services in the package.") + g.P("// Source: ", filepath.Base(serviceConfigFile), ".") + g.P("const ServiceConfig = `", string(data), "`") + } + } + return nil +} + +func (p *plugin) resolveServiceConfigFile(service *protogen.Service) string { + parentPackageName := string(service.Desc.ParentFile().Package().Parent().Name()) + fileName := parentPackageName + "_grpc_service_config.json" + return filepath.Join(p.path, filepath.Dir(service.Location.SourceFile), fileName) +} + +func (p *plugin) validate(required bool) error { + addr, cleanup, err := p.startLocalServer() + if err != nil { + return err + } + defer cleanup() + for _, file := range p.gen.Files { + if !file.Generate { + continue + } + for _, service := range file.Services { + serviceConfigFile := p.resolveServiceConfigFile(service) + if required { + if _, err := os.Stat(serviceConfigFile); err != nil { + return fmt.Errorf( + "validate: missing service config file %s for %s (see: %s)", + serviceConfigFile, + service.Desc.FullName(), + docURL, + ) + } + } + data, err := ioutil.ReadFile(serviceConfigFile) + if err != nil { + return err + } + // gRPC Go validates a service config when dialing. + conn, err := grpc.Dial( + addr, + grpc.WithDefaultServiceConfig(string(data)), + grpc.WithInsecure(), + grpc.WithBlock(), + ) + if err != nil { + return fmt.Errorf("validate: invalid service config %s: %w", serviceConfigFile, err) + } + if err := conn.Close(); err != nil { + return err + } + var serviceConfigContent serviceConfig + if err := json.Unmarshal(data, &serviceConfigContent); err != nil { + return err + } + if required && !serviceConfigContent.hasService(service) { + return fmt.Errorf( + "validate: missing service config for %s in %s (see: %s)", + service.Desc.FullName(), + serviceConfigFile, + docURL, + ) + } + } + } + return nil +} + +type serviceConfig struct { + MethodConfigs []struct { + Names []struct { + Service string + Method string + } `json:"name"` + } `json:"methodConfig"` +} + +func (c serviceConfig) hasService(service *protogen.Service) bool { + for _, methodConfig := range c.MethodConfigs { + for _, name := range methodConfig.Names { + if (name.Service == "" && name.Method == "") || + (name.Service == string(service.Desc.FullName()) && name.Method == "") { + return true + } + } + } + return false +} + +func (p *plugin) startLocalServer() (string, func(), error) { + lis, err := net.Listen("tcp", "localhost:0") + if err != nil { + return "", nil, err + } + localServer := grpc.NewServer() + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + _ = localServer.Serve(lis) + }() + cleanup := func() { + localServer.Stop() + wg.Wait() + } + return lis.Addr().String(), cleanup, nil +} diff --git a/tools/commitlint/.commitlintrc.js b/tools/commitlint/.commitlintrc.js new file mode 100644 index 0000000..92e3aec --- /dev/null +++ b/tools/commitlint/.commitlintrc.js @@ -0,0 +1,8 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + // Treat as warning until Dependabot supports commitlint. + // https://github.com/dependabot/dependabot-core/issues/2445 + "body-max-line-length": [1, "always", 100], + } +}; diff --git a/tools/commitlint/package.json b/tools/commitlint/package.json new file mode 100644 index 0000000..2f66822 --- /dev/null +++ b/tools/commitlint/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "@commitlint/cli": "^11.0.0", + "@commitlint/config-conventional": "^11.0.0" + } +} diff --git a/tools/commitlint/rules.mk b/tools/commitlint/rules.mk new file mode 100644 index 0000000..0d3eb3b --- /dev/null +++ b/tools/commitlint/rules.mk @@ -0,0 +1,16 @@ +commitlint_cwd := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +commitlint := $(commitlint_cwd)/node_modules/.bin/commitlint + +$(commitlint): $(commitlint_cwd)/package.json + $(info [commitlint] installing package...) + @cd $(commitlint_cwd) && npm install --no-save --no-audit &> /dev/null + @touch $@ + +.PHONY: commitlint +commitlint: $(commitlint_cwd)/.commitlintrc.js $(commitlint) + $(info [$@] linting commit messages...) + @git fetch --tags + @NODE_PATH=$(commitlint_cwd)/node_modules $(commitlint) \ + --config $< \ + --from origin/master \ + --to HEAD diff --git a/tools/git-verify-nodiff/git-verify-nodiff.bash b/tools/git-verify-nodiff/git-verify-nodiff.bash new file mode 100755 index 0000000..0dd1f0c --- /dev/null +++ b/tools/git-verify-nodiff/git-verify-nodiff.bash @@ -0,0 +1,9 @@ +#!/bin/bash + +set -euo pipefail + +if [[ -n $(git status --porcelain) ]]; then + echo "Staging area is dirty, please add all files created by the build to .gitignore" + git diff --patch + exit 1 +fi diff --git a/tools/git-verify-nodiff/rules.mk b/tools/git-verify-nodiff/rules.mk new file mode 100644 index 0000000..0f8e590 --- /dev/null +++ b/tools/git-verify-nodiff/rules.mk @@ -0,0 +1,7 @@ +git_verify_nodiff_cwd := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +git_verify_nodiff := $(git_verify_nodiff_cwd)/git-verify-nodiff.bash + +.PHONY: git-verify-nodiff +git-verify-nodiff: + $(info [$@] verifying that git has no diff...) + @$(git_verify_nodiff) diff --git a/tools/golangci-lint/rules.mk b/tools/golangci-lint/rules.mk new file mode 100644 index 0000000..119f35c --- /dev/null +++ b/tools/golangci-lint/rules.mk @@ -0,0 +1,24 @@ +golangci_lint_cwd := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +golangci_lint_version := 1.41.1 +golangci_lint := $(golangci_lint_cwd)/$(golangci_lint_version)/golangci-lint + +ifeq ($(shell uname),Linux) +golangci_lint_archive_url := https://github.com/golangci/golangci-lint/releases/download/v${golangci_lint_version}/golangci-lint-${golangci_lint_version}-linux-amd64.tar.gz +else ifeq ($(shell uname),Darwin) +golangci_lint_archive_url := https://github.com/golangci/golangci-lint/releases/download/v${golangci_lint_version}/golangci-lint-${golangci_lint_version}-darwin-amd64.tar.gz +else +$(error unsupported OS: $(shell uname)) +endif + +$(golangci_lint): + $(info [golangci-lint] fetching version $(golangci_lint) binary...) + @mkdir -p $(dir $@) + @curl -sSL $(golangci_lint_archive_url) -o - | \ + tar -xz --directory $(dir $@) --strip-components 1 + @chmod +x $@ + @touch $@ + +.PHONY: go-lint +go-lint: $(golangci_lint) + $(info [$@] linting Go code...) + @$(golangci_lint) run diff --git a/tools/goreview/rules.mk b/tools/goreview/rules.mk new file mode 100644 index 0000000..9687199 --- /dev/null +++ b/tools/goreview/rules.mk @@ -0,0 +1,18 @@ +goreview_cwd := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +goreview_version := 0.16.0 +goreview := $(goreview_cwd)/$(goreview_version)/goreview + +goreview_archive_url := https://github.com/einride/goreview/releases/download/v$(goreview_version)/goreview_$(goreview_version)_$(shell uname)_$(shell uname -m).tar.gz + +$(goreview): $(goreview_cwd)/rules.mk + $(info [go-review] fetching $(goreview_version) binary...) + @mkdir -p $(dir $@) + @curl -sSL $(goreview_archive_url) -o - | tar -xz --directory $(dir $@) + @chmod +x $@ + @touch $@ + +# go-review: review Go code for Einride-specific conventions +.PHONY: go-review +go-review: $(goreview) + $(info [$@] reviewing Go code for Einride-specific conventions...) + @$(goreview) -c 1 ./... diff --git a/tools/prettier/.prettierignore b/tools/prettier/.prettierignore new file mode 100644 index 0000000..2e4c9ed --- /dev/null +++ b/tools/prettier/.prettierignore @@ -0,0 +1,5 @@ +# paths are relative to this file + +../ +../vendor +../node_modules diff --git a/tools/prettier/package.json b/tools/prettier/package.json new file mode 100644 index 0000000..9caf44e --- /dev/null +++ b/tools/prettier/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "prettier": "^2.2.1" + } +} diff --git a/tools/prettier/rules.mk b/tools/prettier/rules.mk new file mode 100644 index 0000000..ab6b6b7 --- /dev/null +++ b/tools/prettier/rules.mk @@ -0,0 +1,17 @@ +prettier_cwd := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +prettier := $(prettier_cwd)/node_modules/.bin/prettier + +$(prettier): $(prettier_cwd)/package.json + $(info [prettier] installing...) + @cd $(prettier_cwd) && npm install --no-save --no-audit &> /dev/null + @touch $@ + +.PHONY: prettier-markdown +prettier-markdown: $(prettier_cwd)/.prettierignore $(prettier) + $(info [$@] formatting Markdown files...) + @$(prettier) \ + --loglevel warn \ + --ignore-path $< \ + --parser markdown \ + --prose-wrap always \ + --write **/*.md diff --git a/tools/semantic-release/.releaserc.yaml b/tools/semantic-release/.releaserc.yaml new file mode 100644 index 0000000..1a68877 --- /dev/null +++ b/tools/semantic-release/.releaserc.yaml @@ -0,0 +1,19 @@ +plugins: + - - "@semantic-release/commit-analyzer" + - preset: "conventionalcommits" + releaseRules: + # Given Go v2+ conventions we disable major releases on + # breaking changes and leave it up to the developer + # to make major releases + - breaking: true + release: "minor" + - "@semantic-release/release-notes-generator" + - "@semantic-release/github" + +branches: ["master"] +# github plugin is the only one running this step and we're not interested +# in its updates to PR and issues +success: false +# github plugin is the only one running this step and we're not interested +# the issues it creates due to failed releases +fail: false diff --git a/tools/semantic-release/package.json b/tools/semantic-release/package.json new file mode 100644 index 0000000..3069fdc --- /dev/null +++ b/tools/semantic-release/package.json @@ -0,0 +1,8 @@ +{ + "devDependencies": { + "semantic-release": "^17.3.0", + "@semantic-release/github": "^7.2.0", + "@semantic-release/release-notes-generator": "^9.0.1", + "conventional-changelog-conventionalcommits": "^4.5.0" + } +} diff --git a/tools/semantic-release/rules.mk b/tools/semantic-release/rules.mk new file mode 100644 index 0000000..2992c1a --- /dev/null +++ b/tools/semantic-release/rules.mk @@ -0,0 +1,12 @@ +semantic_release_cwd := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +semantic_release := $(semantic_release_cwd)/node_modules/.bin/semantic-release + +$(semantic_release): $(semantic_release_cwd)/package.json + $(info [semantic-release] installing packages...) + @cd $(semantic_release_cwd) && npm install --no-save --no-audit --ignore-scripts &> /dev/null + @touch $@ + +.PHONY: semantic-release +semantic-release: $(semantic_release_cwd)/.releaserc.yaml $(semantic_release) + $(info [$@] creating release...) + @cd $(semantic_release_cwd) && $(semantic_release)