Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlejandro Rodríguez <alejorro70@gmail.com>2019-09-24 05:58:59 +0300
committerAlejandro Rodríguez <alejorro70@gmail.com>2019-09-27 23:59:52 +0300
commit762b47cc7444ed857d776e4457e36f386f2e8a9a (patch)
treec16a50a9d76432fa2961437ccf04332b4301c529
parent94ab4e3d76d08721d7a2d9f82835bb651b130a9a (diff)
Migrate gitaly-proto linter from gitaly-proto
-rw-r--r--Makefile2
-rw-r--r--_support/Makefile.template5
-rw-r--r--doc/beginners_guide.md7
-rw-r--r--doc/design_ha.md2
-rw-r--r--internal/praefect/protoregistry/protoregistry.go2
-rw-r--r--proto/DEPRECATION.md2
-rw-r--r--proto/go/internal/cmd/protoc-gen-gitaly/main.go240
-rw-r--r--proto/go/internal/extension.go31
-rw-r--r--proto/go/internal/filedescriptor.go34
-rw-r--r--proto/go/internal/linter/lint.go68
-rw-r--r--proto/go/internal/linter/lint_test.go50
-rw-r--r--proto/go/internal/linter/method.go219
-rw-r--r--proto/go/internal/linter/testdata/invalid.pb.go195
-rw-r--r--proto/go/internal/linter/testdata/invalid.proto95
-rw-r--r--proto/go/internal/linter/testdata/valid.pb.go283
-rw-r--r--proto/go/internal/linter/testdata/valid.proto86
16 files changed, 1311 insertions, 10 deletions
diff --git a/Makefile b/Makefile
index a36b2d2b7..89b8a5c8a 100644
--- a/Makefile
+++ b/Makefile
@@ -51,7 +51,7 @@ prepare-tests: prepare-build
.PHONY: test
test: prepare-build
cd $(BUILD_DIR) && $(MAKE) $@
-
+
.PHONY: test-with-proxies
test-with-proxies: prepare-build
cd $(BUILD_DIR) && $(MAKE) $@
diff --git a/_support/Makefile.template b/_support/Makefile.template
index 986d8338f..592c1e7f9 100644
--- a/_support/Makefile.template
+++ b/_support/Makefile.template
@@ -23,7 +23,7 @@ export GOPROXY ?= https://proxy.golang.org
all: build
{{ .Git2GoVendorDir }}/.ok:
- rm -rf {{ .Git2GoVendorDir }}
+ rm -rf {{ .Git2GoVendorDir }}
mkdir -p {{ .Git2GoVendorDir }}
cd {{ .Git2GoVendorDir }} && curl -L -o libgit2.tar.gz https://github.com/libgit2/libgit2/archive/v{{ .LibGit2Version }}.tar.gz
@@ -271,8 +271,7 @@ proto: {{ .ProtoC }} {{ .ProtoCGenGo }} {{ .ProtoCGenGitaly }} {{ .GrpcToolsRuby
go get github.com/golang/protobuf/protoc-gen-go@v1.3.2
{{ .ProtoCGenGitaly }}:
- # Todo fix protoc-gen-gitaly versioning
- go install gitlab.com/gitlab-org/gitaly-proto/go/internal/cmd/protoc-gen-gitaly
+ cd {{ .SourceDir }}/proto/go/internal && go build -o $@ gitlab.com/gitlab-org/gitaly/proto/go/internal/cmd/protoc-gen-gitaly
{{ .GrpcToolsRuby }}:
gem install --bindir {{ .BuildDir }}/bin -v 1.0.1 grpc-tools
diff --git a/doc/beginners_guide.md b/doc/beginners_guide.md
index 58281f171..d2d0fdc74 100644
--- a/doc/beginners_guide.md
+++ b/doc/beginners_guide.md
@@ -38,7 +38,7 @@ integrates into GitLab as a whole.
You have the following options to combine GOPATH and GDK:
-1. (default and recommended) use the embedded Gitaly GOPATH in GDK
+1. (default and recommended) use the embedded Gitaly GOPATH in GDK
2. (if you like having a global GOPATH) run `go get
gitlab.com/gitlab-org/gitaly` in your global GOPATH, and symlink
`/path/to/gdk/gitaly/src/gitlab.com/gitlab-org/gitaly` to
@@ -121,7 +121,7 @@ service is named either after the Git CLI command, or after the Git
object type.
If either your request or response data can exceed 100KB you need to use the
-`stream` keyword. To generate the server and client code, run `make proto`.
+`stream` keyword. To generate the server and client code, run `make proto`.
##### Gitaly
@@ -243,7 +243,7 @@ Generally, you should always write new tests in Go even when testing Ruby code,
since we're planning to gradually rewrite everything in Go and want to avoid
having to rewrite the tests as well.
-To run the full test suite, use `make test`.
+To run the full test suite, use `make test`.
You'll need some [test repositories](test_repos.md), you can set these up with `make prepare-tests`.
#### Go tests
@@ -316,6 +316,7 @@ To use your custom Gitaly when running Rails tests in GDK, go to the
[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/#getting-started
[git-remote]: https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes
[gitaly]: https://gitlab.com/gitlab-org/gitaly
+[gitaly-proto]: https://gitlab.com/gitlab-org/gitaly/tree/master/proto
[gitaly-issue]: https://gitlab.com/gitlab-org/gitaly/issues
[gitlab]: https://gitlab.com
[go-workspace]: https://golang.org/doc/code.html#Workspaces
diff --git a/doc/design_ha.md b/doc/design_ha.md
index fb3cf523d..477225dcb 100644
--- a/doc/design_ha.md
+++ b/doc/design_ha.md
@@ -155,7 +155,7 @@ When the beta nears completion further stages will be defined.
* Cons
* Process heavy; requires custom tooling
* Requires a way to tell which methods are read/write
- * [See WIP for marking modifying RPCs](https://gitlab.com/gitlab-org/gitaly-proto/merge_requests/228)
+ * [See MR for marking modifying RPCs](https://gitlab.com/gitlab-org/gitaly-proto/merge_requests/228)
* See also:
* [nRPC](https://github.com/nats-rpc/nrpc) - gRPC via NATS
* [grpclb](https://github.com/bsm/grpclb) - gRPC load balancer
diff --git a/internal/praefect/protoregistry/protoregistry.go b/internal/praefect/protoregistry/protoregistry.go
index 21c5254ff..931da1461 100644
--- a/internal/praefect/protoregistry/protoregistry.go
+++ b/internal/praefect/protoregistry/protoregistry.go
@@ -76,7 +76,7 @@ var protoScope = map[gitalypb.OperationMsg_Scope]Scope{
}
// MethodInfo contains metadata about the RPC method. Refer to documentation
-// for message type "OperationMsg" shared.proto in gitlab-org/gitaly-proto for
+// for message type "OperationMsg" shared.proto in ./proto for
// more documentation.
type MethodInfo struct {
Operation OpType
diff --git a/proto/DEPRECATION.md b/proto/DEPRECATION.md
index f2f145bc0..18bd0b44e 100644
--- a/proto/DEPRECATION.md
+++ b/proto/DEPRECATION.md
@@ -8,7 +8,7 @@ issue description.
```
We are deprecating RPC FooBar because **REASONS**.
-- [ ] put a deprecation comment `// DEPRECATED: <ISSUE-LINK>` in gitaly-proto **Merge Request LINK**
+- [ ] put a deprecation comment `// DEPRECATED: <ISSUE-LINK>` in ./proto **Merge Request LINK**
- [ ] find all client-side uses of RPC and list below
- [ ] update all client-side uses to no longer use RPC **ADD Merge Request LINKS**
- [ ] wait for a GitLab release in which the RPC is no longer occurring in client side code **LINK TO GITLAB-CE RELEASE TAG**
diff --git a/proto/go/internal/cmd/protoc-gen-gitaly/main.go b/proto/go/internal/cmd/protoc-gen-gitaly/main.go
new file mode 100644
index 000000000..7aac43b72
--- /dev/null
+++ b/proto/go/internal/cmd/protoc-gen-gitaly/main.go
@@ -0,0 +1,240 @@
+/*Command protoc-gen-gitaly is designed to be used as a protobuf compiler
+plugin to verify Gitaly processes are being followed when writing RPC's.
+
+Usage
+
+The protoc-gen-gitaly linter can be chained into any protoc workflow that
+requires verification that Gitaly RPC guidelines are followed. Typically
+this can be done by adding the following argument to an existing protoc
+command:
+
+ --gitaly_out=.
+
+For example, you may add the linter as an argument to the command responsible
+for generating Go code:
+
+ protoc --go_out=. --gitaly_out=. *.proto
+
+Or, you can run the Gitaly linter by itself. To try out, run the following
+command while in the project root:
+
+ protoc --gitaly_out=. ./go/internal/linter/testdata/incomplete.proto
+
+You should see some errors printed to screen for improperly written
+RPC's in the incomplete.proto file.
+
+Prerequisites
+
+The protobuf compiler (protoc) can be obtained from the GitHub page:
+https://github.com/protocolbuffers/protobuf/releases
+
+Background
+
+The protobuf compiler accepts plugins to analyze protobuf files and generate
+language specific code.
+
+These plugins require the following executable naming convention:
+
+ protoc-gen-$NAME
+
+Where $NAME is the plugin name of the compiler desired. The protobuf compiler
+will search the PATH until an executable with that name is found for a
+desired plugin. For example, the following protoc command:
+
+ protoc --gitaly_out=. *.proto
+
+The above will search the PATH for an executable named protoc-gen-gitaly
+
+The plugin accepts a protobuf message in STDIN that describes the parsed
+protobuf files. A response is sent back on STDOUT that contains any errors.
+*/
+package main
+
+import (
+ "bytes"
+ "compress/gzip"
+ "fmt"
+ "go/format"
+ "io"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+ "text/template"
+
+ "github.com/golang/protobuf/proto"
+ "github.com/golang/protobuf/protoc-gen-go/descriptor"
+ plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
+ "gitlab.com/gitlab-org/gitaly/proto/go/internal/linter"
+)
+
+const (
+ gitalyProtoDirArg = "proto_dir"
+ gitalypbDirArg = "gitalypb_dir"
+)
+
+func main() {
+ data, err := ioutil.ReadAll(os.Stdin)
+ if err != nil {
+ log.Fatalf("reading input: %s", err)
+ }
+
+ req := new(plugin.CodeGeneratorRequest)
+
+ if err := proto.Unmarshal(data, req); err != nil {
+ log.Fatalf("parsing input proto: %s", err)
+ }
+
+ if err = lintProtos(req); err != nil {
+ log.Fatal(err)
+ }
+
+ if err = generateProtolistGo(req); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func parseArgs(argString string) (gitalyProtoDir string, gitalypbDir string) {
+ for _, arg := range strings.Split(argString, ",") {
+ argKeyValue := strings.Split(arg, "=")
+ if len(argKeyValue) != 2 {
+ continue
+ }
+ switch argKeyValue[0] {
+ case gitalyProtoDirArg:
+ gitalyProtoDir = argKeyValue[1]
+ case gitalypbDirArg:
+ gitalypbDir = argKeyValue[1]
+ }
+ }
+
+ return gitalyProtoDir, gitalypbDir
+}
+
+func lintProtos(req *plugin.CodeGeneratorRequest) error {
+ var errMsgs []string
+ for _, pf := range req.GetProtoFile() {
+ errs := linter.LintFile(pf)
+ for _, err := range errs {
+ errMsgs = append(errMsgs, err.Error())
+ }
+ }
+
+ resp := new(plugin.CodeGeneratorResponse)
+
+ if len(errMsgs) > 0 {
+ errMsg := strings.Join(errMsgs, "\n\t")
+ resp.Error = &errMsg
+ }
+
+ // Send back the results.
+ data, err := proto.Marshal(resp)
+ if err != nil {
+ return fmt.Errorf("failed to marshal output proto: %s", err)
+ }
+
+ _, err = os.Stdout.Write(data)
+ if err != nil {
+ return fmt.Errorf("failed to write output proto: %s", err)
+ }
+ return nil
+}
+
+func generateProtolistGo(req *plugin.CodeGeneratorRequest) error {
+ var err error
+ gitalyProtoDir, gitalypbDir := parseArgs(req.GetParameter())
+
+ if gitalyProtoDir == "" {
+ return fmt.Errorf("%s not provided", gitalyProtoDirArg)
+ }
+ if gitalypbDir == "" {
+ return fmt.Errorf("%s not provided", gitalypbDirArg)
+ }
+
+ var protoNames []string
+
+ if gitalyProtoDir, err = filepath.Abs(gitalyProtoDir); err != nil {
+ return fmt.Errorf("failed to get absolute path for %s: %v", gitalyProtoDir, err)
+ }
+
+ files, err := ioutil.ReadDir(gitalyProtoDir)
+ if err != nil {
+ return fmt.Errorf("failed to read %s: %v", gitalyProtoDir, err)
+ }
+
+ for _, fi := range files {
+ if !fi.IsDir() && strings.HasSuffix(fi.Name(), ".proto") {
+ protoNames = append(protoNames, fmt.Sprintf(`"%s"`, fi.Name()))
+ }
+ }
+
+ f, err := os.Create(filepath.Join(gitalypbDir, "protolist.go"))
+ if err != nil {
+ return fmt.Errorf("could not create protolist.go: %v", err)
+ }
+ defer f.Close()
+
+ if err = renderProtoList(f, protoNames); err != nil {
+ return fmt.Errorf("could not render go code: %v", err)
+ }
+
+ return nil
+}
+
+// renderProtoList generate a go file with a list of gitaly protos
+func renderProtoList(dest io.WriteCloser, protoNames []string) error {
+ var joinFunc = template.FuncMap{"join": strings.Join}
+ protoList := `package gitalypb
+ // Code generated by protoc-gen-gitaly. DO NOT EDIT
+
+ // GitalyProtos is a list of gitaly protobuf files
+ var GitalyProtos = []string{
+ {{join . ",\n"}},
+ }
+ `
+ protoListTempl, err := template.New("protoList").Funcs(joinFunc).Parse(protoList)
+ if err != nil {
+ return fmt.Errorf("could not create go code template: %v", err)
+ }
+
+ var rawGo bytes.Buffer
+
+ if err := protoListTempl.Execute(&rawGo, protoNames); err != nil {
+ return fmt.Errorf("could not execute go code template: %v", err)
+ }
+
+ formattedGo, err := format.Source(rawGo.Bytes())
+ if err != nil {
+ return fmt.Errorf("could not format go code: %v", err)
+ }
+
+ if _, err = io.Copy(dest, bytes.NewBuffer(formattedGo)); err != nil {
+ return fmt.Errorf("failed to write protolist.go file: %v", err)
+ }
+
+ return nil
+}
+
+// extractFile extracts a FileDescriptorProto from a gzip'd buffer.
+// Note: function is copied from the github.com/golang/protobuf dependency:
+// https://github.com/golang/protobuf/blob/9eb2c01ac278a5d89ce4b2be68fe4500955d8179/descriptor/descriptor.go#L50
+func extractFile(gz []byte) (*descriptor.FileDescriptorProto, error) {
+ r, err := gzip.NewReader(bytes.NewReader(gz))
+ if err != nil {
+ return nil, fmt.Errorf("failed to open gzip reader: %v", err)
+ }
+ defer r.Close()
+
+ b, err := ioutil.ReadAll(r)
+ if err != nil {
+ return nil, fmt.Errorf("failed to uncompress descriptor: %v", err)
+ }
+
+ fd := new(descriptor.FileDescriptorProto)
+ if err := proto.Unmarshal(b, fd); err != nil {
+ return nil, fmt.Errorf("malformed FileDescriptorProto: %v", err)
+ }
+
+ return fd, nil
+}
diff --git a/proto/go/internal/extension.go b/proto/go/internal/extension.go
new file mode 100644
index 000000000..1918cd949
--- /dev/null
+++ b/proto/go/internal/extension.go
@@ -0,0 +1,31 @@
+package internal
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/golang/protobuf/proto"
+ "github.com/golang/protobuf/protoc-gen-go/descriptor"
+ "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
+)
+
+// GetOpExtension gets the OperationMsg from a method descriptor
+func GetOpExtension(m *descriptor.MethodDescriptorProto) (*gitalypb.OperationMsg, error) {
+ options := m.GetOptions()
+
+ if !proto.HasExtension(options, gitalypb.E_OpType) {
+ return nil, errors.New("missing op_type option")
+ }
+
+ ext, err := proto.GetExtension(options, gitalypb.E_OpType)
+ if err != nil {
+ return nil, err
+ }
+
+ opMsg, ok := ext.(*gitalypb.OperationMsg)
+ if !ok {
+ return nil, fmt.Errorf("unable to obtain OperationMsg from %#v", ext)
+ }
+
+ return opMsg, nil
+}
diff --git a/proto/go/internal/filedescriptor.go b/proto/go/internal/filedescriptor.go
new file mode 100644
index 000000000..ef0214149
--- /dev/null
+++ b/proto/go/internal/filedescriptor.go
@@ -0,0 +1,34 @@
+package internal
+
+import (
+ "bytes"
+ "compress/gzip"
+ "fmt"
+ "io/ioutil"
+
+ "github.com/golang/protobuf/proto"
+ "github.com/golang/protobuf/protoc-gen-go/descriptor"
+)
+
+// ExtractFile extracts a FileDescriptorProto from a gzip'd buffer.
+// Note: function is copied from the github.com/golang/protobuf dependency:
+// https://github.com/golang/protobuf/blob/9eb2c01ac278a5d89ce4b2be68fe4500955d8179/descriptor/descriptor.go#L50
+func ExtractFile(gz []byte) (*descriptor.FileDescriptorProto, error) {
+ r, err := gzip.NewReader(bytes.NewReader(gz))
+ if err != nil {
+ return nil, fmt.Errorf("failed to open gzip reader: %v", err)
+ }
+ defer r.Close()
+
+ b, err := ioutil.ReadAll(r)
+ if err != nil {
+ return nil, fmt.Errorf("failed to uncompress descriptor: %v", err)
+ }
+
+ fd := new(descriptor.FileDescriptorProto)
+ if err := proto.Unmarshal(b, fd); err != nil {
+ return nil, fmt.Errorf("malformed FileDescriptorProto: %v", err)
+ }
+
+ return fd, nil
+}
diff --git a/proto/go/internal/linter/lint.go b/proto/go/internal/linter/lint.go
new file mode 100644
index 000000000..bfdc37393
--- /dev/null
+++ b/proto/go/internal/linter/lint.go
@@ -0,0 +1,68 @@
+package linter
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/golang/protobuf/protoc-gen-go/descriptor"
+ "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
+ "gitlab.com/gitlab-org/gitaly/proto/go/internal"
+)
+
+// ensureMethodOpType will ensure that method includes the op_type option.
+// See proto example below:
+//
+// rpc ExampleMethod(ExampleMethodRequest) returns (ExampleMethodResponse) {
+// option (op_type).op = ACCESSOR;
+// }
+func ensureMethodOpType(fileDesc *descriptor.FileDescriptorProto, m *descriptor.MethodDescriptorProto) error {
+ opMsg, err := internal.GetOpExtension(m)
+ if err != nil {
+ return err
+ }
+
+ ml := methodLinter{
+ fileDesc: fileDesc,
+ methodDesc: m,
+ opMsg: opMsg,
+ }
+
+ switch opCode := opMsg.GetOp(); opCode {
+
+ case gitalypb.OperationMsg_ACCESSOR:
+ return ml.validateAccessor()
+
+ case gitalypb.OperationMsg_MUTATOR:
+ // if mutator, we need to make sure we specify scope or target repo
+ return ml.validateMutator()
+
+ case gitalypb.OperationMsg_UNKNOWN:
+ return errors.New("op set to UNKNOWN")
+
+ default:
+ return fmt.Errorf("invalid operation class with int32 value of %d", opCode)
+ }
+}
+
+// LintFile ensures the file described meets Gitaly required processes.
+// Currently, this is limited to validating if request messages contain
+// a mandatory operation code.
+func LintFile(file *descriptor.FileDescriptorProto) []error {
+ var errs []error
+
+ for _, serviceDescriptorProto := range file.GetService() {
+ for _, methodDescriptorProto := range serviceDescriptorProto.GetMethod() {
+ err := ensureMethodOpType(file, methodDescriptorProto)
+ if err != nil {
+ // decorate error with current file and method
+ err = fmt.Errorf(
+ "%s: Method %q: %s",
+ file.GetName(), methodDescriptorProto.GetName(), err,
+ )
+ errs = append(errs, err)
+ }
+ }
+ }
+
+ return errs
+}
diff --git a/proto/go/internal/linter/lint_test.go b/proto/go/internal/linter/lint_test.go
new file mode 100644
index 000000000..b0828cc20
--- /dev/null
+++ b/proto/go/internal/linter/lint_test.go
@@ -0,0 +1,50 @@
+package linter_test
+
+import (
+ "errors"
+ "testing"
+
+ "github.com/golang/protobuf/proto"
+ "github.com/stretchr/testify/require"
+
+ "gitlab.com/gitlab-org/gitaly/proto/go/internal"
+ "gitlab.com/gitlab-org/gitaly/proto/go/internal/linter"
+ _ "gitlab.com/gitlab-org/gitaly/proto/go/internal/linter/testdata"
+)
+
+func TestLintFile(t *testing.T) {
+ for _, tt := range []struct {
+ protoPath string
+ errs []error
+ }{
+ {
+ protoPath: "go/internal/linter/testdata/valid.proto",
+ errs: nil,
+ },
+ {
+ protoPath: "go/internal/linter/testdata/invalid.proto",
+ errs: []error{
+ errors.New(`go/internal/linter/testdata/invalid.proto: Method "InvalidMethod0": missing op_type option`),
+ errors.New(`go/internal/linter/testdata/invalid.proto: Method "InvalidMethod1": op set to UNKNOWN`),
+ errors.New(`go/internal/linter/testdata/invalid.proto: Method "InvalidMethod2": missing target repository field`),
+ errors.New(`go/internal/linter/testdata/invalid.proto: Method "InvalidMethod3": server level scoped RPC should not specify target repo`),
+ errors.New(`go/internal/linter/testdata/invalid.proto: Method "InvalidMethod4": missing target repository field`),
+ errors.New(`go/internal/linter/testdata/invalid.proto: Method "InvalidMethod5": unable to parse target field OID 🐛: strconv.Atoi: parsing "🐛": invalid syntax`),
+ errors.New(`go/internal/linter/testdata/invalid.proto: Method "InvalidMethod6": target repo OID [1] does not exist in request message`),
+ errors.New(`go/internal/linter/testdata/invalid.proto: Method "InvalidMethod7": unexpected type TYPE_INT32 (expected .gitaly.Repository) for target repo field addressed by [1]`),
+ errors.New(`go/internal/linter/testdata/invalid.proto: Method "InvalidMethod8": expected 1-th field of OID [1 1] to be TYPE_MESSAGE, but got TYPE_INT32`),
+ errors.New(`go/internal/linter/testdata/invalid.proto: Method "InvalidMethod9": target repo OID [1 2] does not exist in request message`),
+ errors.New(`go/internal/linter/testdata/invalid.proto: Method "InvalidMethod10": unexpected type TYPE_INT32 (expected .gitaly.Repository) for target repo field addressed by [1 1]`),
+ errors.New(`go/internal/linter/testdata/invalid.proto: Method "InvalidMethod11": storage level scoped RPC should not specify target repo`),
+ },
+ },
+ } {
+ t.Run(tt.protoPath, func(t *testing.T) {
+ fd, err := internal.ExtractFile(proto.FileDescriptor(tt.protoPath))
+ require.NoError(t, err)
+
+ errs := linter.LintFile(fd)
+ require.Equal(t, tt.errs, errs)
+ })
+ }
+}
diff --git a/proto/go/internal/linter/method.go b/proto/go/internal/linter/method.go
new file mode 100644
index 000000000..c6396cc98
--- /dev/null
+++ b/proto/go/internal/linter/method.go
@@ -0,0 +1,219 @@
+package linter
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/golang/protobuf/proto"
+ "github.com/golang/protobuf/protoc-gen-go/descriptor"
+ "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
+ "gitlab.com/gitlab-org/gitaly/proto/go/internal"
+)
+
+type methodLinter struct {
+ fileDesc *descriptor.FileDescriptorProto
+ methodDesc *descriptor.MethodDescriptorProto
+ opMsg *gitalypb.OperationMsg
+}
+
+// validateAccessor will ensure the accessor method does not specify a target
+// repo
+func (ml methodLinter) validateAccessor() error {
+ switch ml.opMsg.GetScopeLevel() {
+ case gitalypb.OperationMsg_REPOSITORY:
+ return ml.ensureValidRepoScope()
+ case gitalypb.OperationMsg_STORAGE:
+ return ml.ensureValidStorageScope()
+ }
+
+ return nil
+}
+
+// validateMutator will ensure the following rules:
+// - Mutator RPC's with repository level scope must specify a target repo
+// - Mutator RPC's without target repo must not be scoped at repo level
+func (ml methodLinter) validateMutator() error {
+ switch scope := ml.opMsg.GetScopeLevel(); scope {
+
+ case gitalypb.OperationMsg_REPOSITORY:
+ return ml.ensureValidRepoScope()
+
+ case gitalypb.OperationMsg_SERVER:
+ return ml.ensureValidServerScope()
+
+ case gitalypb.OperationMsg_STORAGE:
+ return ml.ensureValidStorageScope()
+
+ default:
+ return fmt.Errorf("unknown operation scope level %d", scope)
+
+ }
+}
+
+// TODO: add checks for storage location via valid field annotation for Gitaly HA
+func (ml methodLinter) ensureValidStorageScope() error {
+ if ml.opMsg.GetTargetRepositoryField() != "" {
+ return errors.New("storage level scoped RPC should not specify target repo")
+ }
+ return nil
+}
+
+func (ml methodLinter) ensureValidServerScope() error {
+ if ml.opMsg.GetTargetRepositoryField() != "" {
+ return errors.New("server level scoped RPC should not specify target repo")
+ }
+ return nil
+}
+
+func (ml methodLinter) ensureValidRepoScope() error {
+ return ml.ensureValidTargetRepo()
+}
+
+const repoTypeName = ".gitaly.Repository"
+
+func (ml methodLinter) ensureValidTargetRepo() error {
+ if ml.opMsg.GetTargetRepositoryField() == "" {
+ return errors.New("missing target repository field")
+ }
+
+ oids, err := parseOID(ml.opMsg.GetTargetRepositoryField())
+ if err != nil {
+ return err
+ }
+
+ sharedMsgs, err := getSharedTypes()
+ if err != nil {
+ return err
+ }
+
+ topLevelMsgs := map[string]*descriptor.DescriptorProto{}
+ for _, msg := range append(ml.fileDesc.GetMessageType(), sharedMsgs...) {
+ topLevelMsgs[msg.GetName()] = msg
+ }
+
+ reqMsgName, err := lastName(ml.methodDesc.GetInputType())
+ if err != nil {
+ return err
+ }
+
+ msgT := topLevelMsgs[reqMsgName]
+ targetType := ""
+ visited := 0
+
+ // TODO: Improve code quality by removing OID_FIELDS and MSG_FIELDS labels
+OID_FIELDS:
+ for i, fieldNo := range oids {
+ fields := msgT.GetField()
+ MSG_FIELDS:
+ for _, f := range fields {
+ if f.GetNumber() == int32(fieldNo) {
+ visited++
+
+ targetType = f.GetTypeName()
+ if targetType == "" {
+ // primitives like int32 don't have TypeName
+ targetType = f.GetType().String()
+ }
+
+ // if last OID, we're done
+ if i == len(oids)-1 {
+ break OID_FIELDS
+ }
+
+ // if not last OID, descend into next nested message
+ const msgPrimitive = "TYPE_MESSAGE"
+ if primitive := f.GetType().String(); primitive != msgPrimitive {
+ return fmt.Errorf(
+ "expected %d-th field of OID %+v to be %s, but got %s",
+ i+1, oids, msgPrimitive, primitive,
+ )
+ }
+
+ msgName, err := lastName(f.GetTypeName())
+ if err != nil {
+ return err
+ }
+
+ // first check if field refers to a nested type
+ for _, nestedType := range msgT.GetNestedType() {
+ if msgName == nestedType.GetName() {
+ msgT = nestedType
+ break MSG_FIELDS
+ }
+ }
+
+ // then, check if field refers to a top level type
+ var ok bool
+ msgT, ok = topLevelMsgs[msgName]
+ if !ok {
+ return fmt.Errorf(
+ "could not find message type %q for %d-th element %d of target OID %+v",
+ msgName, i+1, fieldNo, oids,
+ )
+ }
+ break
+ }
+ }
+ }
+
+ if visited != len(oids) {
+ return fmt.Errorf("target repo OID %+v does not exist in request message", oids)
+ }
+
+ if targetType != repoTypeName {
+ return fmt.Errorf(
+ "unexpected type %s (expected %s) for target repo field addressed by %+v",
+ targetType, repoTypeName, oids,
+ )
+ }
+
+ return nil
+}
+
+func lastName(inputType string) (string, error) {
+ tokens := strings.Split(inputType, ".")
+
+ msgName := tokens[len(tokens)-1]
+ if msgName == "" {
+ return "", fmt.Errorf("unable to parse method input type: %s", inputType)
+ }
+
+ return msgName, nil
+}
+
+// parses a string like "1.1" and returns a slice of ints
+func parseOID(rawFieldUID string) ([]int, error) {
+ fieldNoStrs := strings.Split(rawFieldUID, ".")
+
+ if len(fieldNoStrs) < 1 {
+ return nil,
+ fmt.Errorf("OID string contains no field numbers: %s", fieldNoStrs)
+ }
+
+ fieldNos := make([]int, len(fieldNoStrs))
+
+ for i, fieldNoStr := range fieldNoStrs {
+ fieldNo, err := strconv.Atoi(fieldNoStr)
+ if err != nil {
+ return nil,
+ fmt.Errorf("unable to parse target field OID %s: %s", rawFieldUID, err)
+ }
+ if fieldNo < 1 {
+ return nil, errors.New("zero is an invalid field number")
+ }
+ fieldNos[i] = fieldNo
+ }
+
+ return fieldNos, nil
+}
+
+func getSharedTypes() ([]*descriptor.DescriptorProto, error) {
+ sharedFD, err := internal.ExtractFile(proto.FileDescriptor("shared.proto"))
+ if err != nil {
+ return nil, err
+ }
+
+ return sharedFD.GetMessageType(), nil
+}
diff --git a/proto/go/internal/linter/testdata/invalid.pb.go b/proto/go/internal/linter/testdata/invalid.pb.go
new file mode 100644
index 000000000..f04421e42
--- /dev/null
+++ b/proto/go/internal/linter/testdata/invalid.pb.go
@@ -0,0 +1,195 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: go/internal/linter/testdata/invalid.proto
+
+package test
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+import _ "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type InvalidMethodRequest struct {
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *InvalidMethodRequest) Reset() { *m = InvalidMethodRequest{} }
+func (m *InvalidMethodRequest) String() string { return proto.CompactTextString(m) }
+func (*InvalidMethodRequest) ProtoMessage() {}
+func (*InvalidMethodRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_invalid_1fd1290907d704eb, []int{0}
+}
+func (m *InvalidMethodRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_InvalidMethodRequest.Unmarshal(m, b)
+}
+func (m *InvalidMethodRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_InvalidMethodRequest.Marshal(b, m, deterministic)
+}
+func (dst *InvalidMethodRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_InvalidMethodRequest.Merge(dst, src)
+}
+func (m *InvalidMethodRequest) XXX_Size() int {
+ return xxx_messageInfo_InvalidMethodRequest.Size(m)
+}
+func (m *InvalidMethodRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_InvalidMethodRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_InvalidMethodRequest proto.InternalMessageInfo
+
+type InvalidTargetType struct {
+ WrongType int32 `protobuf:"varint,1,opt,name=wrong_type,json=wrongType,proto3" json:"wrong_type,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *InvalidTargetType) Reset() { *m = InvalidTargetType{} }
+func (m *InvalidTargetType) String() string { return proto.CompactTextString(m) }
+func (*InvalidTargetType) ProtoMessage() {}
+func (*InvalidTargetType) Descriptor() ([]byte, []int) {
+ return fileDescriptor_invalid_1fd1290907d704eb, []int{1}
+}
+func (m *InvalidTargetType) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_InvalidTargetType.Unmarshal(m, b)
+}
+func (m *InvalidTargetType) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_InvalidTargetType.Marshal(b, m, deterministic)
+}
+func (dst *InvalidTargetType) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_InvalidTargetType.Merge(dst, src)
+}
+func (m *InvalidTargetType) XXX_Size() int {
+ return xxx_messageInfo_InvalidTargetType.Size(m)
+}
+func (m *InvalidTargetType) XXX_DiscardUnknown() {
+ xxx_messageInfo_InvalidTargetType.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_InvalidTargetType proto.InternalMessageInfo
+
+func (m *InvalidTargetType) GetWrongType() int32 {
+ if m != nil {
+ return m.WrongType
+ }
+ return 0
+}
+
+type InvalidMethodResponse struct {
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *InvalidMethodResponse) Reset() { *m = InvalidMethodResponse{} }
+func (m *InvalidMethodResponse) String() string { return proto.CompactTextString(m) }
+func (*InvalidMethodResponse) ProtoMessage() {}
+func (*InvalidMethodResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_invalid_1fd1290907d704eb, []int{2}
+}
+func (m *InvalidMethodResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_InvalidMethodResponse.Unmarshal(m, b)
+}
+func (m *InvalidMethodResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_InvalidMethodResponse.Marshal(b, m, deterministic)
+}
+func (dst *InvalidMethodResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_InvalidMethodResponse.Merge(dst, src)
+}
+func (m *InvalidMethodResponse) XXX_Size() int {
+ return xxx_messageInfo_InvalidMethodResponse.Size(m)
+}
+func (m *InvalidMethodResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_InvalidMethodResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_InvalidMethodResponse proto.InternalMessageInfo
+
+type InvalidNestedRequest struct {
+ InnerMessage *InvalidTargetType `protobuf:"bytes,1,opt,name=inner_message,json=innerMessage,proto3" json:"inner_message,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *InvalidNestedRequest) Reset() { *m = InvalidNestedRequest{} }
+func (m *InvalidNestedRequest) String() string { return proto.CompactTextString(m) }
+func (*InvalidNestedRequest) ProtoMessage() {}
+func (*InvalidNestedRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_invalid_1fd1290907d704eb, []int{3}
+}
+func (m *InvalidNestedRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_InvalidNestedRequest.Unmarshal(m, b)
+}
+func (m *InvalidNestedRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_InvalidNestedRequest.Marshal(b, m, deterministic)
+}
+func (dst *InvalidNestedRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_InvalidNestedRequest.Merge(dst, src)
+}
+func (m *InvalidNestedRequest) XXX_Size() int {
+ return xxx_messageInfo_InvalidNestedRequest.Size(m)
+}
+func (m *InvalidNestedRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_InvalidNestedRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_InvalidNestedRequest proto.InternalMessageInfo
+
+func (m *InvalidNestedRequest) GetInnerMessage() *InvalidTargetType {
+ if m != nil {
+ return m.InnerMessage
+ }
+ return nil
+}
+
+func init() {
+ proto.RegisterType((*InvalidMethodRequest)(nil), "test.InvalidMethodRequest")
+ proto.RegisterType((*InvalidTargetType)(nil), "test.InvalidTargetType")
+ proto.RegisterType((*InvalidMethodResponse)(nil), "test.InvalidMethodResponse")
+ proto.RegisterType((*InvalidNestedRequest)(nil), "test.InvalidNestedRequest")
+}
+
+func init() {
+ proto.RegisterFile("go/internal/linter/testdata/invalid.proto", fileDescriptor_invalid_1fd1290907d704eb)
+}
+
+var fileDescriptor_invalid_1fd1290907d704eb = []byte{
+ // 379 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0xd4, 0xcd, 0x4e, 0x83, 0x40,
+ 0x10, 0x07, 0x70, 0xb7, 0xb6, 0x95, 0x6e, 0x5b, 0xad, 0x1b, 0xb5, 0x06, 0x63, 0x62, 0x38, 0xe1,
+ 0x05, 0x0a, 0xf5, 0x33, 0xf1, 0x05, 0x8c, 0xa9, 0x89, 0x2d, 0xb1, 0xf1, 0xd4, 0xa0, 0x9d, 0x50,
+ 0x92, 0x0a, 0xc8, 0xae, 0x35, 0x7d, 0x0b, 0x3d, 0x71, 0xf0, 0xe0, 0x2b, 0x7a, 0xe6, 0x64, 0x0a,
+ 0xd8, 0x84, 0xa5, 0x26, 0x0d, 0xf5, 0x46, 0x66, 0x27, 0xbf, 0xfd, 0x2f, 0xcc, 0x82, 0x8f, 0x2d,
+ 0x57, 0xb5, 0x1d, 0x06, 0xbe, 0x63, 0x8e, 0xd5, 0x71, 0xf4, 0xa4, 0x32, 0xa0, 0x6c, 0x68, 0x32,
+ 0x53, 0xb5, 0x9d, 0x89, 0x39, 0xb6, 0x87, 0x8a, 0xe7, 0xbb, 0xcc, 0x25, 0xc5, 0x59, 0x5d, 0xac,
+ 0xd1, 0x91, 0xe9, 0x43, 0x52, 0x93, 0xf6, 0xf0, 0xce, 0x75, 0xdc, 0xd4, 0x01, 0x36, 0x72, 0x87,
+ 0x5d, 0x78, 0x79, 0x05, 0xca, 0x24, 0x1d, 0x6f, 0x27, 0x75, 0xc3, 0xf4, 0x2d, 0x60, 0xc6, 0xd4,
+ 0x03, 0x72, 0x88, 0xf1, 0x9b, 0xef, 0x3a, 0xd6, 0x80, 0x4d, 0x3d, 0xd8, 0x47, 0x47, 0x48, 0x2e,
+ 0x75, 0x2b, 0x51, 0x65, 0xb6, 0x2c, 0x35, 0xf1, 0x2e, 0x67, 0x51, 0xcf, 0x75, 0x28, 0x48, 0xc6,
+ 0x7c, 0x93, 0x5b, 0xa0, 0x0c, 0x7e, 0x37, 0x21, 0x57, 0xb8, 0x6e, 0x3b, 0x0e, 0xf8, 0x83, 0x67,
+ 0xa0, 0xd4, 0xb4, 0x62, 0xb2, 0xaa, 0x37, 0x95, 0x59, 0x50, 0x25, 0xb3, 0x7f, 0xb7, 0x16, 0x75,
+ 0x77, 0xe2, 0x66, 0xfd, 0x43, 0xc0, 0x9b, 0x49, 0x4f, 0x0f, 0xfc, 0x89, 0xfd, 0x04, 0xe4, 0x66,
+ 0x5e, 0x89, 0x13, 0xb4, 0x88, 0x98, 0xb2, 0x52, 0x67, 0x14, 0x0f, 0x16, 0xae, 0x25, 0x99, 0xd7,
+ 0xc8, 0x1d, 0x87, 0x69, 0xf9, 0xb1, 0x72, 0x18, 0xc8, 0x05, 0x21, 0x4b, 0xea, 0xab, 0x92, 0x05,
+ 0x72, 0xcf, 0x91, 0xed, 0xfc, 0x64, 0x35, 0x0c, 0xe4, 0x0d, 0x01, 0x35, 0x90, 0x88, 0xb4, 0x4c,
+ 0xd4, 0x93, 0x55, 0xa3, 0x22, 0xd2, 0xe7, 0xc8, 0xd3, 0xfc, 0x64, 0x2d, 0x0c, 0x64, 0x41, 0x40,
+ 0x62, 0xf1, 0xfb, 0xeb, 0xfd, 0x93, 0x18, 0x1c, 0x7c, 0x96, 0x1f, 0xae, 0x84, 0x81, 0x5c, 0x12,
+ 0x16, 0xbe, 0x81, 0x73, 0xf2, 0xd7, 0x60, 0x2e, 0x4d, 0xf6, 0x38, 0xf2, 0x22, 0x27, 0x99, 0x7c,
+ 0x29, 0x71, 0x5d, 0x53, 0xb4, 0xcc, 0x04, 0x5c, 0x72, 0xa7, 0x4f, 0xdd, 0xb9, 0xa5, 0x5d, 0x9d,
+ 0xf4, 0xf1, 0x56, 0x7a, 0xfe, 0x5b, 0xff, 0x02, 0x6b, 0xe4, 0x81, 0x87, 0x57, 0xb8, 0x59, 0xf5,
+ 0x30, 0x90, 0x2b, 0x02, 0x6a, 0x14, 0x22, 0xfa, 0xb1, 0x1c, 0xfd, 0xd5, 0xda, 0x3f, 0x01, 0x00,
+ 0x00, 0xff, 0xff, 0x1d, 0xd6, 0x03, 0xa0, 0x16, 0x05, 0x00, 0x00,
+}
diff --git a/proto/go/internal/linter/testdata/invalid.proto b/proto/go/internal/linter/testdata/invalid.proto
new file mode 100644
index 000000000..18bea7596
--- /dev/null
+++ b/proto/go/internal/linter/testdata/invalid.proto
@@ -0,0 +1,95 @@
+syntax = "proto3";
+
+package test;
+
+import "shared.proto";
+
+message InvalidMethodRequest {}
+
+message InvalidTargetType {
+ int32 wrong_type = 1; // RPC specifies field 1 is target repo
+}
+
+message InvalidMethodResponse{}
+
+message InvalidNestedRequest{
+ InvalidTargetType inner_message = 1;
+}
+
+service InvalidService {
+ // should fail if op_type extension is missing
+ rpc InvalidMethod0(InvalidMethodRequest) returns (InvalidMethodResponse) {}
+
+ // should fail if op type is unknown
+ rpc InvalidMethod1(InvalidMethodRequest) returns (InvalidMethodResponse) {
+ option (gitaly.op_type).op = UNKNOWN;
+ }
+ // should fail if target repo is not provided for accessor
+ rpc InvalidMethod2(InvalidMethodRequest) returns (InvalidMethodResponse) {
+ option (gitaly.op_type) = {
+ op: ACCESSOR
+ };
+ }
+ // should fail if target repo is provided for server-scoped mutator
+ rpc InvalidMethod3(InvalidMethodRequest) returns (InvalidMethodResponse) {
+ option (gitaly.op_type) = {
+ op: MUTATOR
+ scope_level: SERVER
+ target_repository_field: "1"
+ };
+ }
+ // should fail if missing either target repo or non-repo-scope for mutator
+ rpc InvalidMethod4(InvalidMethodRequest) returns (InvalidMethodResponse) {
+ option (gitaly.op_type).op = MUTATOR;
+ }
+ // should fail if target repo is incorrectly formatted for mutator
+ rpc InvalidMethod5(InvalidMethodRequest) returns (InvalidMethodResponse) {
+ option (gitaly.op_type) = {
+ op: MUTATOR
+ target_repository_field: "🐛"
+ };
+ }
+ // should fail if target field specified does not exist in request message
+ rpc InvalidMethod6(InvalidMethodRequest) returns (InvalidMethodResponse) {
+ option (gitaly.op_type) = {
+ op: MUTATOR
+ target_repository_field: "1"
+ };
+ }
+ // should fail if target field type is not of type Repository
+ rpc InvalidMethod7(InvalidTargetType) returns (InvalidMethodResponse) {
+ option (gitaly.op_type) = {
+ op: MUTATOR
+ target_repository_field: "1"
+ };
+ }
+ // should fail if request message is not deep enough for specified OID
+ rpc InvalidMethod8(InvalidTargetType) returns (InvalidMethodResponse) {
+ option (gitaly.op_type) = {
+ op: MUTATOR
+ target_repository_field: "1.1"
+ };
+ }
+ // should fail if nested target field type is missing
+ rpc InvalidMethod9(InvalidNestedRequest) returns (InvalidMethodResponse) {
+ option (gitaly.op_type) = {
+ op: MUTATOR
+ target_repository_field: "1.2"
+ };
+ }
+ // should fail if nested target field type is not of type Repository
+ rpc InvalidMethod10(InvalidNestedRequest) returns (InvalidMethodResponse) {
+ option (gitaly.op_type) = {
+ op: MUTATOR
+ target_repository_field: "1.1"
+ };
+ }
+ // should fail if target repo is specified for storage scoped RPC
+ rpc InvalidMethod11(InvalidMethodRequest) returns (InvalidMethodResponse) {
+ option (gitaly.op_type) = {
+ op: MUTATOR
+ scope_level: STORAGE
+ target_repository_field: "1.1"
+ };
+ }
+} \ No newline at end of file
diff --git a/proto/go/internal/linter/testdata/valid.pb.go b/proto/go/internal/linter/testdata/valid.pb.go
new file mode 100644
index 000000000..a79b9b664
--- /dev/null
+++ b/proto/go/internal/linter/testdata/valid.pb.go
@@ -0,0 +1,283 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: go/internal/linter/testdata/valid.proto
+
+package test
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+import gitalypb "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type ValidRequest struct {
+ Destination *gitalypb.Repository `protobuf:"bytes,1,opt,name=destination,proto3" json:"destination,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ValidRequest) Reset() { *m = ValidRequest{} }
+func (m *ValidRequest) String() string { return proto.CompactTextString(m) }
+func (*ValidRequest) ProtoMessage() {}
+func (*ValidRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_valid_28fe26eda283975a, []int{0}
+}
+func (m *ValidRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ValidRequest.Unmarshal(m, b)
+}
+func (m *ValidRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ValidRequest.Marshal(b, m, deterministic)
+}
+func (dst *ValidRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ValidRequest.Merge(dst, src)
+}
+func (m *ValidRequest) XXX_Size() int {
+ return xxx_messageInfo_ValidRequest.Size(m)
+}
+func (m *ValidRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_ValidRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ValidRequest proto.InternalMessageInfo
+
+func (m *ValidRequest) GetDestination() *gitalypb.Repository {
+ if m != nil {
+ return m.Destination
+ }
+ return nil
+}
+
+type ValidResponse struct {
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ValidResponse) Reset() { *m = ValidResponse{} }
+func (m *ValidResponse) String() string { return proto.CompactTextString(m) }
+func (*ValidResponse) ProtoMessage() {}
+func (*ValidResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_valid_28fe26eda283975a, []int{1}
+}
+func (m *ValidResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ValidResponse.Unmarshal(m, b)
+}
+func (m *ValidResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ValidResponse.Marshal(b, m, deterministic)
+}
+func (dst *ValidResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ValidResponse.Merge(dst, src)
+}
+func (m *ValidResponse) XXX_Size() int {
+ return xxx_messageInfo_ValidResponse.Size(m)
+}
+func (m *ValidResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_ValidResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ValidResponse proto.InternalMessageInfo
+
+type ValidNestedRequest struct {
+ InnerMessage *ValidRequest `protobuf:"bytes,1,opt,name=inner_message,json=innerMessage,proto3" json:"inner_message,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ValidNestedRequest) Reset() { *m = ValidNestedRequest{} }
+func (m *ValidNestedRequest) String() string { return proto.CompactTextString(m) }
+func (*ValidNestedRequest) ProtoMessage() {}
+func (*ValidNestedRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_valid_28fe26eda283975a, []int{2}
+}
+func (m *ValidNestedRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ValidNestedRequest.Unmarshal(m, b)
+}
+func (m *ValidNestedRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ValidNestedRequest.Marshal(b, m, deterministic)
+}
+func (dst *ValidNestedRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ValidNestedRequest.Merge(dst, src)
+}
+func (m *ValidNestedRequest) XXX_Size() int {
+ return xxx_messageInfo_ValidNestedRequest.Size(m)
+}
+func (m *ValidNestedRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_ValidNestedRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ValidNestedRequest proto.InternalMessageInfo
+
+func (m *ValidNestedRequest) GetInnerMessage() *ValidRequest {
+ if m != nil {
+ return m.InnerMessage
+ }
+ return nil
+}
+
+type ValidNestedSharedRequest struct {
+ NestedTargetRepo *gitalypb.ObjectPool `protobuf:"bytes,1,opt,name=nested_target_repo,json=nestedTargetRepo,proto3" json:"nested_target_repo,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ValidNestedSharedRequest) Reset() { *m = ValidNestedSharedRequest{} }
+func (m *ValidNestedSharedRequest) String() string { return proto.CompactTextString(m) }
+func (*ValidNestedSharedRequest) ProtoMessage() {}
+func (*ValidNestedSharedRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_valid_28fe26eda283975a, []int{3}
+}
+func (m *ValidNestedSharedRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ValidNestedSharedRequest.Unmarshal(m, b)
+}
+func (m *ValidNestedSharedRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ValidNestedSharedRequest.Marshal(b, m, deterministic)
+}
+func (dst *ValidNestedSharedRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ValidNestedSharedRequest.Merge(dst, src)
+}
+func (m *ValidNestedSharedRequest) XXX_Size() int {
+ return xxx_messageInfo_ValidNestedSharedRequest.Size(m)
+}
+func (m *ValidNestedSharedRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_ValidNestedSharedRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ValidNestedSharedRequest proto.InternalMessageInfo
+
+func (m *ValidNestedSharedRequest) GetNestedTargetRepo() *gitalypb.ObjectPool {
+ if m != nil {
+ return m.NestedTargetRepo
+ }
+ return nil
+}
+
+type ValidInnerNestedRequest struct {
+ Header *ValidInnerNestedRequest_Header `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ValidInnerNestedRequest) Reset() { *m = ValidInnerNestedRequest{} }
+func (m *ValidInnerNestedRequest) String() string { return proto.CompactTextString(m) }
+func (*ValidInnerNestedRequest) ProtoMessage() {}
+func (*ValidInnerNestedRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_valid_28fe26eda283975a, []int{4}
+}
+func (m *ValidInnerNestedRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ValidInnerNestedRequest.Unmarshal(m, b)
+}
+func (m *ValidInnerNestedRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ValidInnerNestedRequest.Marshal(b, m, deterministic)
+}
+func (dst *ValidInnerNestedRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ValidInnerNestedRequest.Merge(dst, src)
+}
+func (m *ValidInnerNestedRequest) XXX_Size() int {
+ return xxx_messageInfo_ValidInnerNestedRequest.Size(m)
+}
+func (m *ValidInnerNestedRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_ValidInnerNestedRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ValidInnerNestedRequest proto.InternalMessageInfo
+
+func (m *ValidInnerNestedRequest) GetHeader() *ValidInnerNestedRequest_Header {
+ if m != nil {
+ return m.Header
+ }
+ return nil
+}
+
+type ValidInnerNestedRequest_Header struct {
+ Destination *gitalypb.Repository `protobuf:"bytes,1,opt,name=destination,proto3" json:"destination,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ValidInnerNestedRequest_Header) Reset() { *m = ValidInnerNestedRequest_Header{} }
+func (m *ValidInnerNestedRequest_Header) String() string { return proto.CompactTextString(m) }
+func (*ValidInnerNestedRequest_Header) ProtoMessage() {}
+func (*ValidInnerNestedRequest_Header) Descriptor() ([]byte, []int) {
+ return fileDescriptor_valid_28fe26eda283975a, []int{4, 0}
+}
+func (m *ValidInnerNestedRequest_Header) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ValidInnerNestedRequest_Header.Unmarshal(m, b)
+}
+func (m *ValidInnerNestedRequest_Header) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ValidInnerNestedRequest_Header.Marshal(b, m, deterministic)
+}
+func (dst *ValidInnerNestedRequest_Header) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ValidInnerNestedRequest_Header.Merge(dst, src)
+}
+func (m *ValidInnerNestedRequest_Header) XXX_Size() int {
+ return xxx_messageInfo_ValidInnerNestedRequest_Header.Size(m)
+}
+func (m *ValidInnerNestedRequest_Header) XXX_DiscardUnknown() {
+ xxx_messageInfo_ValidInnerNestedRequest_Header.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ValidInnerNestedRequest_Header proto.InternalMessageInfo
+
+func (m *ValidInnerNestedRequest_Header) GetDestination() *gitalypb.Repository {
+ if m != nil {
+ return m.Destination
+ }
+ return nil
+}
+
+func init() {
+ proto.RegisterType((*ValidRequest)(nil), "test.ValidRequest")
+ proto.RegisterType((*ValidResponse)(nil), "test.ValidResponse")
+ proto.RegisterType((*ValidNestedRequest)(nil), "test.ValidNestedRequest")
+ proto.RegisterType((*ValidNestedSharedRequest)(nil), "test.ValidNestedSharedRequest")
+ proto.RegisterType((*ValidInnerNestedRequest)(nil), "test.ValidInnerNestedRequest")
+ proto.RegisterType((*ValidInnerNestedRequest_Header)(nil), "test.ValidInnerNestedRequest.Header")
+}
+
+func init() {
+ proto.RegisterFile("go/internal/linter/testdata/valid.proto", fileDescriptor_valid_28fe26eda283975a)
+}
+
+var fileDescriptor_valid_28fe26eda283975a = []byte{
+ // 408 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0xd3, 0x41, 0x8b, 0xd3, 0x40,
+ 0x14, 0x07, 0x70, 0x66, 0xad, 0x35, 0x4e, 0xba, 0xb8, 0x8c, 0x07, 0x4b, 0x40, 0x91, 0x20, 0x98,
+ 0x53, 0x42, 0xbb, 0xab, 0xeb, 0x41, 0xa4, 0x82, 0x07, 0xf7, 0xd0, 0x5d, 0xc9, 0x2e, 0x9e, 0x84,
+ 0x32, 0x6d, 0x1e, 0xe9, 0x48, 0x9c, 0x89, 0x33, 0xcf, 0x42, 0x3f, 0x49, 0xbe, 0x91, 0x1f, 0xaa,
+ 0x27, 0xc9, 0x34, 0xd6, 0x89, 0x2d, 0x4b, 0xdb, 0x5b, 0x78, 0xfc, 0xf3, 0xcb, 0x9b, 0xf7, 0x26,
+ 0xf4, 0x75, 0xae, 0x12, 0x21, 0x11, 0xb4, 0xe4, 0x45, 0x52, 0xd8, 0xa7, 0x04, 0xc1, 0x60, 0xc6,
+ 0x91, 0x27, 0x0b, 0x5e, 0x88, 0x2c, 0x2e, 0xb5, 0x42, 0xc5, 0x3a, 0x75, 0x35, 0xe8, 0x99, 0x39,
+ 0xd7, 0xd0, 0xd4, 0xc2, 0x4f, 0xb4, 0xf7, 0xb5, 0x8e, 0xa4, 0xf0, 0xf3, 0x17, 0x18, 0x64, 0x17,
+ 0xd4, 0xcf, 0xc0, 0xa0, 0x90, 0x1c, 0x85, 0x92, 0x7d, 0xf2, 0x92, 0x44, 0xfe, 0x90, 0xc5, 0xb9,
+ 0x40, 0x5e, 0x2c, 0xe3, 0x14, 0x4a, 0x65, 0x04, 0x2a, 0xbd, 0x4c, 0xdd, 0x58, 0xf8, 0x84, 0x9e,
+ 0x36, 0x8a, 0x29, 0x95, 0x34, 0x10, 0x8e, 0x29, 0xb3, 0x85, 0x6b, 0x30, 0x08, 0x1b, 0xfc, 0x92,
+ 0x9e, 0x0a, 0x29, 0x41, 0x4f, 0x7e, 0x80, 0x31, 0x3c, 0x87, 0x0d, 0x5f, 0x37, 0x16, 0xbb, 0x7d,
+ 0xa4, 0x3d, 0x1b, 0x1c, 0xaf, 0x73, 0xe1, 0x37, 0xda, 0x77, 0xb8, 0x5b, 0x7b, 0x80, 0xbf, 0xe8,
+ 0x88, 0x32, 0x69, 0xcb, 0x13, 0xe4, 0x3a, 0x07, 0x9c, 0x68, 0x28, 0xd5, 0xff, 0x8d, 0xdf, 0x4c,
+ 0xbf, 0xc3, 0x0c, 0xbf, 0x28, 0x55, 0xa4, 0x67, 0xeb, 0xf4, 0x9d, 0x0d, 0xd7, 0x07, 0x0a, 0x2b,
+ 0x42, 0x9f, 0x59, 0xfe, 0xaa, 0xfe, 0x66, 0xbb, 0xe5, 0xf7, 0xb4, 0x3b, 0x07, 0x9e, 0x81, 0x6e,
+ 0xc4, 0x57, 0x4e, 0xaf, 0xdb, 0xf1, 0xf8, 0xb3, 0xcd, 0xa6, 0xcd, 0x3b, 0xc1, 0x07, 0xda, 0x5d,
+ 0x57, 0x8e, 0x9b, 0xeb, 0xf0, 0x77, 0xa7, 0x59, 0xcf, 0x2d, 0xe8, 0x85, 0x98, 0x01, 0x1b, 0x51,
+ 0x7a, 0x07, 0x06, 0xc7, 0x80, 0x73, 0x95, 0xb1, 0x1d, 0x83, 0x0b, 0x9e, 0xb6, 0x6a, 0xcd, 0x3a,
+ 0x1e, 0xaf, 0xaa, 0xe8, 0xa1, 0x77, 0x12, 0x90, 0x01, 0xfb, 0x48, 0xfd, 0x7f, 0xc2, 0xf0, 0x50,
+ 0x82, 0x6c, 0x11, 0xe7, 0x47, 0x11, 0x23, 0x97, 0xb8, 0xd8, 0x9f, 0xf0, 0x56, 0x55, 0xd4, 0xf1,
+ 0xc8, 0x19, 0x61, 0x57, 0xae, 0xf0, 0x86, 0xf5, 0x9d, 0x74, 0x6b, 0x25, 0xbb, 0x1d, 0x7f, 0x55,
+ 0x45, 0x8f, 0x3c, 0x12, 0x3c, 0x18, 0xc4, 0x03, 0x76, 0xe3, 0x52, 0x6f, 0xd9, 0x8b, 0x2d, 0xaa,
+ 0x75, 0xe1, 0xf6, 0x00, 0xaf, 0x5d, 0xf0, 0x92, 0x3d, 0xbf, 0xf7, 0xce, 0xec, 0xe1, 0xb5, 0xa6,
+ 0xf5, 0xee, 0xf0, 0x69, 0x9d, 0x4c, 0xbb, 0xf6, 0x6f, 0x3f, 0xff, 0x13, 0x00, 0x00, 0xff, 0xff,
+ 0x0e, 0x93, 0x1a, 0xd9, 0x2c, 0x04, 0x00, 0x00,
+}
diff --git a/proto/go/internal/linter/testdata/valid.proto b/proto/go/internal/linter/testdata/valid.proto
new file mode 100644
index 000000000..792807644
--- /dev/null
+++ b/proto/go/internal/linter/testdata/valid.proto
@@ -0,0 +1,86 @@
+syntax = "proto3";
+
+package test;
+
+import "shared.proto";
+
+message ValidRequest {
+ gitaly.Repository destination = 1;
+}
+
+message ValidResponse{}
+
+message ValidNestedRequest{
+ ValidRequest inner_message = 1;
+}
+
+message ValidNestedSharedRequest {
+ gitaly.ObjectPool nested_target_repo = 1;
+}
+
+message ValidInnerNestedRequest {
+ message Header {
+ gitaly.Repository destination = 1;
+ }
+
+ Header header = 1;
+}
+
+service ValidService {
+ rpc TestMethod(ValidRequest) returns (ValidResponse) {
+ option (gitaly.op_type) = {
+ op: ACCESSOR
+ target_repository_field: "1"
+ };
+ }
+
+ rpc TestMethod2(ValidRequest) returns (ValidResponse) {
+ option (gitaly.op_type) = {
+ op: MUTATOR
+ target_repository_field: "1"
+ };
+ }
+
+ rpc TestMethod3(ValidRequest) returns (ValidResponse) {
+ option (gitaly.op_type) = {
+ op: MUTATOR
+ scope_level: REPOSITORY // repo can be explicitly included
+ target_repository_field: "1"
+ };
+ }
+
+ rpc TestMethod4(ValidRequest) returns (ValidResponse) {
+ option (gitaly.op_type) = {
+ op: MUTATOR
+ scope_level: SERVER
+ };
+ }
+
+ rpc TestMethod5(ValidNestedRequest) returns (ValidResponse) {
+ option (gitaly.op_type) = {
+ op: MUTATOR
+ target_repository_field: "1.1"
+ };
+ }
+
+ rpc TestMethod6(ValidNestedSharedRequest) returns (ValidResponse) {
+ option (gitaly.op_type) = {
+ op: MUTATOR
+ target_repository_field: "1.1"
+ };
+ }
+
+ rpc TestMethod7(ValidInnerNestedRequest) returns (ValidResponse) {
+ option (gitaly.op_type) = {
+ op: MUTATOR
+ target_repository_field: "1.1"
+ };
+ }
+
+ rpc TestMethod8(ValidRequest) returns (ValidResponse) {
+ option (gitaly.op_type) = {
+ op: MUTATOR
+ scope_level: STORAGE
+ };
+ }
+}