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:
authorPavlo Strokov <pstrokov@gitlab.com>2020-03-02 19:31:26 +0300
committerPavlo Strokov <pstrokov@gitlab.com>2020-03-02 19:31:26 +0300
commit0f65bca8b7b172afa1c22e31a74c1e1d8480025d (patch)
treed8b6a27bbd1de067b81b31daf087cd550fad3615
parent0dbd71bb41ae937916e98c9684cdd2ad38be51b2 (diff)
parentf3f4ae8bc2945b8363835e6c6862a7044311aa9e (diff)
Merge branch 'po-list-repos' into 'master'
New service InternalGitaly and RPC WalkRepos Closes #2473 See merge request gitlab-org/gitaly!1855
-rw-r--r--changelogs/unreleased/po-list-repos.yml5
-rw-r--r--internal/service/internalgitaly/server.go14
-rw-r--r--internal/service/internalgitaly/testhelper_test.go41
-rw-r--r--internal/service/internalgitaly/walkrepos.go68
-rw-r--r--internal/service/internalgitaly/walkrepos_test.go64
-rw-r--r--internal/service/register.go3
-rw-r--r--proto/go/gitalypb/internal.pb.go240
-rw-r--r--proto/go/gitalypb/protolist.go1
-rw-r--r--proto/internal.proto28
-rw-r--r--ruby/proto/gitaly.rb2
-rw-r--r--ruby/proto/gitaly/internal_pb.rb19
-rw-r--r--ruby/proto/gitaly/internal_services_pb.rb26
12 files changed, 511 insertions, 0 deletions
diff --git a/changelogs/unreleased/po-list-repos.yml b/changelogs/unreleased/po-list-repos.yml
new file mode 100644
index 000000000..d7e461678
--- /dev/null
+++ b/changelogs/unreleased/po-list-repos.yml
@@ -0,0 +1,5 @@
+---
+title: Internal RPC for walking Gitaly repos
+merge_request: 1855
+author:
+type: added
diff --git a/internal/service/internalgitaly/server.go b/internal/service/internalgitaly/server.go
new file mode 100644
index 000000000..19f5170aa
--- /dev/null
+++ b/internal/service/internalgitaly/server.go
@@ -0,0 +1,14 @@
+package internalgitaly
+
+import (
+ "gitlab.com/gitlab-org/gitaly/internal/config"
+ "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
+)
+
+type server struct {
+ storages []config.Storage
+}
+
+func NewServer(storages []config.Storage) gitalypb.InternalGitalyServer {
+ return &server{storages: storages}
+}
diff --git a/internal/service/internalgitaly/testhelper_test.go b/internal/service/internalgitaly/testhelper_test.go
new file mode 100644
index 000000000..093de37b9
--- /dev/null
+++ b/internal/service/internalgitaly/testhelper_test.go
@@ -0,0 +1,41 @@
+package internalgitaly
+
+import (
+ "net"
+ "testing"
+
+ "gitlab.com/gitlab-org/gitaly/internal/config"
+ "gitlab.com/gitlab-org/gitaly/internal/testhelper"
+ "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/reflection"
+)
+
+func runInternalGitalyServer(t *testing.T) (*grpc.Server, string) {
+ serverSocketPath := testhelper.GetTemporaryGitalySocketFileName()
+ grpcServer := testhelper.NewTestGrpcServer(t, nil, nil)
+
+ listener, err := net.Listen("unix", serverSocketPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ gitalypb.RegisterInternalGitalyServer(grpcServer, NewServer(config.Config.Storages))
+ reflection.Register(grpcServer)
+
+ go grpcServer.Serve(listener)
+
+ return grpcServer, "unix://" + serverSocketPath
+}
+
+func newInternalGitalyClient(t *testing.T, serverSocketPath string) (gitalypb.InternalGitalyClient, *grpc.ClientConn) {
+ connOpts := []grpc.DialOption{
+ grpc.WithInsecure(),
+ }
+ conn, err := grpc.Dial(serverSocketPath, connOpts...)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ return gitalypb.NewInternalGitalyClient(conn), conn
+}
diff --git a/internal/service/internalgitaly/walkrepos.go b/internal/service/internalgitaly/walkrepos.go
new file mode 100644
index 000000000..4f167094c
--- /dev/null
+++ b/internal/service/internalgitaly/walkrepos.go
@@ -0,0 +1,68 @@
+package internalgitaly
+
+import (
+ "context"
+ "os"
+ "path/filepath"
+
+ "gitlab.com/gitlab-org/gitaly/internal/helper"
+ "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+)
+
+func (s *server) WalkRepos(req *gitalypb.WalkReposRequest, stream gitalypb.InternalGitaly_WalkReposServer) error {
+ sPath, err := s.storagePath(req.GetStorageName())
+ if err != nil {
+ return err
+ }
+
+ return walkStorage(stream.Context(), sPath, stream)
+}
+
+func (s *server) storagePath(storageName string) (string, error) {
+ for _, storage := range s.storages {
+ if storage.Name == storageName {
+ return storage.Path, nil
+ }
+ }
+ return "", status.Errorf(
+ codes.NotFound,
+ "storage name %q not found", storageName,
+ )
+}
+
+func walkStorage(ctx context.Context, storagePath string, stream gitalypb.InternalGitaly_WalkReposServer) error {
+ return filepath.Walk(storagePath, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ default:
+ // keep walking
+ }
+
+ if helper.IsGitDirectory(path) {
+ relPath, err := filepath.Rel(storagePath, path)
+ if err != nil {
+ return err
+ }
+
+ if err := stream.Send(&gitalypb.WalkReposResponse{
+ RelativePath: relPath,
+ }); err != nil {
+ return err
+ }
+
+ // once we know we are inside a git directory, we don't
+ // want to continue walking inside since that is
+ // resource intensive and unnecessary
+ return filepath.SkipDir
+ }
+
+ return nil
+ })
+}
diff --git a/internal/service/internalgitaly/walkrepos_test.go b/internal/service/internalgitaly/walkrepos_test.go
new file mode 100644
index 000000000..318720334
--- /dev/null
+++ b/internal/service/internalgitaly/walkrepos_test.go
@@ -0,0 +1,64 @@
+package internalgitaly
+
+import (
+ "io"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "gitlab.com/gitlab-org/gitaly/internal/config"
+ "gitlab.com/gitlab-org/gitaly/internal/testhelper"
+ "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+)
+
+func TestWalkRepos(t *testing.T) {
+ server, serverSocketPath := runInternalGitalyServer(t)
+ defer server.Stop()
+
+ client, conn := newInternalGitalyClient(t, serverSocketPath)
+ defer conn.Close()
+
+ testRepo1, _, cleanupFn1 := testhelper.NewTestRepo(t)
+ defer cleanupFn1()
+
+ testRepo2, _, cleanupFn2 := testhelper.NewTestRepo(t)
+ defer cleanupFn2()
+
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ stream, err := client.WalkRepos(ctx, &gitalypb.WalkReposRequest{
+ StorageName: "invalid storage name",
+ })
+ require.NoError(t, err)
+
+ _, err = stream.Recv()
+ require.NotNil(t, err)
+ s, ok := status.FromError(err)
+ require.True(t, ok)
+ require.Equal(t, codes.NotFound, s.Code())
+
+ stream, err = client.WalkRepos(ctx, &gitalypb.WalkReposRequest{
+ StorageName: config.Config.Storages[0].Name,
+ })
+ require.NoError(t, err)
+
+ actualRepos := consumeWalkReposStream(t, stream)
+ require.Contains(t, actualRepos, testRepo1.GetRelativePath())
+ require.Contains(t, actualRepos, testRepo2.GetRelativePath())
+}
+
+func consumeWalkReposStream(t *testing.T, stream gitalypb.InternalGitaly_WalkReposClient) []string {
+ var repos []string
+ for {
+ resp, err := stream.Recv()
+ if err == io.EOF {
+ break
+ } else {
+ require.NoError(t, err)
+ }
+ repos = append(repos, resp.RelativePath)
+ }
+ return repos
+}
diff --git a/internal/service/register.go b/internal/service/register.go
index 119b01fa1..2db6901d7 100644
--- a/internal/service/register.go
+++ b/internal/service/register.go
@@ -1,6 +1,7 @@
package service
import (
+ "gitlab.com/gitlab-org/gitaly/internal/config"
"gitlab.com/gitlab-org/gitaly/internal/rubyserver"
"gitlab.com/gitlab-org/gitaly/internal/service/blob"
"gitlab.com/gitlab-org/gitaly/internal/service/cleanup"
@@ -8,6 +9,7 @@ import (
"gitlab.com/gitlab-org/gitaly/internal/service/conflicts"
"gitlab.com/gitlab-org/gitaly/internal/service/diff"
hook "gitlab.com/gitlab-org/gitaly/internal/service/hooks"
+ "gitlab.com/gitlab-org/gitaly/internal/service/internalgitaly"
"gitlab.com/gitlab-org/gitaly/internal/service/namespace"
"gitlab.com/gitlab-org/gitaly/internal/service/objectpool"
"gitlab.com/gitlab-org/gitaly/internal/service/operations"
@@ -43,6 +45,7 @@ func RegisterAll(grpcServer *grpc.Server, rubyServer *rubyserver.Server) {
gitalypb.RegisterServerServiceServer(grpcServer, server.NewServer())
gitalypb.RegisterObjectPoolServiceServer(grpcServer, objectpool.NewServer())
gitalypb.RegisterHookServiceServer(grpcServer, hook.NewServer())
+ gitalypb.RegisterInternalGitalyServer(grpcServer, internalgitaly.NewServer(config.Config.Storages))
healthpb.RegisterHealthServer(grpcServer, health.NewServer())
}
diff --git a/proto/go/gitalypb/internal.pb.go b/proto/go/gitalypb/internal.pb.go
new file mode 100644
index 000000000..6baf6bf42
--- /dev/null
+++ b/proto/go/gitalypb/internal.pb.go
@@ -0,0 +1,240 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: internal.proto
+
+package gitalypb
+
+import (
+ context "context"
+ fmt "fmt"
+ proto "github.com/golang/protobuf/proto"
+ grpc "google.golang.org/grpc"
+ codes "google.golang.org/grpc/codes"
+ status "google.golang.org/grpc/status"
+ math "math"
+)
+
+// 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.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type WalkReposRequest struct {
+ StorageName string `protobuf:"bytes,1,opt,name=storage_name,json=storageName,proto3" json:"storage_name,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *WalkReposRequest) Reset() { *m = WalkReposRequest{} }
+func (m *WalkReposRequest) String() string { return proto.CompactTextString(m) }
+func (*WalkReposRequest) ProtoMessage() {}
+func (*WalkReposRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_41f4a519b878ee3b, []int{0}
+}
+
+func (m *WalkReposRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_WalkReposRequest.Unmarshal(m, b)
+}
+func (m *WalkReposRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_WalkReposRequest.Marshal(b, m, deterministic)
+}
+func (m *WalkReposRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_WalkReposRequest.Merge(m, src)
+}
+func (m *WalkReposRequest) XXX_Size() int {
+ return xxx_messageInfo_WalkReposRequest.Size(m)
+}
+func (m *WalkReposRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_WalkReposRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_WalkReposRequest proto.InternalMessageInfo
+
+func (m *WalkReposRequest) GetStorageName() string {
+ if m != nil {
+ return m.StorageName
+ }
+ return ""
+}
+
+type WalkReposResponse struct {
+ RelativePath string `protobuf:"bytes,1,opt,name=relative_path,json=relativePath,proto3" json:"relative_path,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *WalkReposResponse) Reset() { *m = WalkReposResponse{} }
+func (m *WalkReposResponse) String() string { return proto.CompactTextString(m) }
+func (*WalkReposResponse) ProtoMessage() {}
+func (*WalkReposResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_41f4a519b878ee3b, []int{1}
+}
+
+func (m *WalkReposResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_WalkReposResponse.Unmarshal(m, b)
+}
+func (m *WalkReposResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_WalkReposResponse.Marshal(b, m, deterministic)
+}
+func (m *WalkReposResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_WalkReposResponse.Merge(m, src)
+}
+func (m *WalkReposResponse) XXX_Size() int {
+ return xxx_messageInfo_WalkReposResponse.Size(m)
+}
+func (m *WalkReposResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_WalkReposResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_WalkReposResponse proto.InternalMessageInfo
+
+func (m *WalkReposResponse) GetRelativePath() string {
+ if m != nil {
+ return m.RelativePath
+ }
+ return ""
+}
+
+func init() {
+ proto.RegisterType((*WalkReposRequest)(nil), "gitaly.WalkReposRequest")
+ proto.RegisterType((*WalkReposResponse)(nil), "gitaly.WalkReposResponse")
+}
+
+func init() { proto.RegisterFile("internal.proto", fileDescriptor_41f4a519b878ee3b) }
+
+var fileDescriptor_41f4a519b878ee3b = []byte{
+ // 233 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xcb, 0xcc, 0x2b, 0x49,
+ 0x2d, 0xca, 0x4b, 0xcc, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x4b, 0xcf, 0x2c, 0x49,
+ 0xcc, 0xa9, 0x94, 0xe2, 0x29, 0xce, 0x48, 0x2c, 0x4a, 0x4d, 0x81, 0x88, 0x2a, 0x59, 0x73, 0x09,
+ 0x84, 0x27, 0xe6, 0x64, 0x07, 0xa5, 0x16, 0xe4, 0x17, 0x07, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97,
+ 0x08, 0xa9, 0x73, 0xf1, 0x14, 0x97, 0xe4, 0x17, 0x25, 0xa6, 0xa7, 0xc6, 0xe7, 0x25, 0xe6, 0xa6,
+ 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x3a, 0xb1, 0x74, 0x1c, 0xd3, 0x61, 0x0c, 0xe2, 0x86, 0xca,
+ 0xf8, 0x25, 0xe6, 0xa6, 0x2a, 0x59, 0x70, 0x09, 0x22, 0x69, 0x2e, 0x2e, 0xc8, 0xcf, 0x2b, 0x4e,
+ 0x15, 0x52, 0xe6, 0xe2, 0x2d, 0x4a, 0xcd, 0x49, 0x2c, 0xc9, 0x2c, 0x4b, 0x8d, 0x2f, 0x48, 0x2c,
+ 0xc9, 0x80, 0x68, 0x0f, 0xe2, 0x81, 0x09, 0x06, 0x24, 0x96, 0x64, 0x18, 0xc5, 0x71, 0xf1, 0x79,
+ 0x42, 0x9d, 0xe7, 0x0e, 0x76, 0x96, 0x90, 0x0f, 0x17, 0x27, 0xdc, 0x2c, 0x21, 0x09, 0x3d, 0x88,
+ 0x63, 0xf5, 0xd0, 0xdd, 0x26, 0x25, 0x89, 0x45, 0x06, 0x62, 0xb1, 0x12, 0xc7, 0xaf, 0xe9, 0x1a,
+ 0x2c, 0x1c, 0x4c, 0x02, 0x4c, 0x06, 0x8c, 0x4e, 0x06, 0x51, 0x20, 0x75, 0x39, 0x89, 0x49, 0x7a,
+ 0xc9, 0xf9, 0xb9, 0xfa, 0x10, 0xa6, 0x6e, 0x7e, 0x51, 0xba, 0x3e, 0x44, 0xb7, 0x3e, 0xd8, 0xf3,
+ 0xfa, 0xe9, 0xf9, 0x50, 0x7e, 0x41, 0x52, 0x12, 0x1b, 0x58, 0xc8, 0x18, 0x10, 0x00, 0x00, 0xff,
+ 0xff, 0xc4, 0xe5, 0xee, 0x29, 0x37, 0x01, 0x00, 0x00,
+}
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ context.Context
+var _ grpc.ClientConn
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+const _ = grpc.SupportPackageIsVersion4
+
+// InternalGitalyClient is the client API for InternalGitaly service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
+type InternalGitalyClient interface {
+ // WalkRepos walks the storage and streams back all known git repos on the
+ // requested storage
+ WalkRepos(ctx context.Context, in *WalkReposRequest, opts ...grpc.CallOption) (InternalGitaly_WalkReposClient, error)
+}
+
+type internalGitalyClient struct {
+ cc *grpc.ClientConn
+}
+
+func NewInternalGitalyClient(cc *grpc.ClientConn) InternalGitalyClient {
+ return &internalGitalyClient{cc}
+}
+
+func (c *internalGitalyClient) WalkRepos(ctx context.Context, in *WalkReposRequest, opts ...grpc.CallOption) (InternalGitaly_WalkReposClient, error) {
+ stream, err := c.cc.NewStream(ctx, &_InternalGitaly_serviceDesc.Streams[0], "/gitaly.InternalGitaly/WalkRepos", opts...)
+ if err != nil {
+ return nil, err
+ }
+ x := &internalGitalyWalkReposClient{stream}
+ if err := x.ClientStream.SendMsg(in); err != nil {
+ return nil, err
+ }
+ if err := x.ClientStream.CloseSend(); err != nil {
+ return nil, err
+ }
+ return x, nil
+}
+
+type InternalGitaly_WalkReposClient interface {
+ Recv() (*WalkReposResponse, error)
+ grpc.ClientStream
+}
+
+type internalGitalyWalkReposClient struct {
+ grpc.ClientStream
+}
+
+func (x *internalGitalyWalkReposClient) Recv() (*WalkReposResponse, error) {
+ m := new(WalkReposResponse)
+ if err := x.ClientStream.RecvMsg(m); err != nil {
+ return nil, err
+ }
+ return m, nil
+}
+
+// InternalGitalyServer is the server API for InternalGitaly service.
+type InternalGitalyServer interface {
+ // WalkRepos walks the storage and streams back all known git repos on the
+ // requested storage
+ WalkRepos(*WalkReposRequest, InternalGitaly_WalkReposServer) error
+}
+
+// UnimplementedInternalGitalyServer can be embedded to have forward compatible implementations.
+type UnimplementedInternalGitalyServer struct {
+}
+
+func (*UnimplementedInternalGitalyServer) WalkRepos(req *WalkReposRequest, srv InternalGitaly_WalkReposServer) error {
+ return status.Errorf(codes.Unimplemented, "method WalkRepos not implemented")
+}
+
+func RegisterInternalGitalyServer(s *grpc.Server, srv InternalGitalyServer) {
+ s.RegisterService(&_InternalGitaly_serviceDesc, srv)
+}
+
+func _InternalGitaly_WalkRepos_Handler(srv interface{}, stream grpc.ServerStream) error {
+ m := new(WalkReposRequest)
+ if err := stream.RecvMsg(m); err != nil {
+ return err
+ }
+ return srv.(InternalGitalyServer).WalkRepos(m, &internalGitalyWalkReposServer{stream})
+}
+
+type InternalGitaly_WalkReposServer interface {
+ Send(*WalkReposResponse) error
+ grpc.ServerStream
+}
+
+type internalGitalyWalkReposServer struct {
+ grpc.ServerStream
+}
+
+func (x *internalGitalyWalkReposServer) Send(m *WalkReposResponse) error {
+ return x.ServerStream.SendMsg(m)
+}
+
+var _InternalGitaly_serviceDesc = grpc.ServiceDesc{
+ ServiceName: "gitaly.InternalGitaly",
+ HandlerType: (*InternalGitalyServer)(nil),
+ Methods: []grpc.MethodDesc{},
+ Streams: []grpc.StreamDesc{
+ {
+ StreamName: "WalkRepos",
+ Handler: _InternalGitaly_WalkRepos_Handler,
+ ServerStreams: true,
+ },
+ },
+ Metadata: "internal.proto",
+}
diff --git a/proto/go/gitalypb/protolist.go b/proto/go/gitalypb/protolist.go
index 236ac7b92..9d85b6eca 100644
--- a/proto/go/gitalypb/protolist.go
+++ b/proto/go/gitalypb/protolist.go
@@ -10,6 +10,7 @@ var GitalyProtos = []string{
"conflicts.proto",
"diff.proto",
"hook.proto",
+ "internal.proto",
"namespace.proto",
"objectpool.proto",
"operations.proto",
diff --git a/proto/internal.proto b/proto/internal.proto
new file mode 100644
index 000000000..235b0f728
--- /dev/null
+++ b/proto/internal.proto
@@ -0,0 +1,28 @@
+syntax = "proto3";
+
+package gitaly;
+
+option go_package = "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb";
+
+import "shared.proto";
+
+// InternalGitaly is a gRPC service meant to be served by a Gitaly node, but
+// only reachable by Praefect or other Gitalies
+service InternalGitaly {
+ // WalkRepos walks the storage and streams back all known git repos on the
+ // requested storage
+ rpc WalkRepos (WalkReposRequest) returns (stream WalkReposResponse) {
+ option (op_type) = {
+ op: ACCESSOR
+ scope_level: STORAGE
+ };
+ }
+}
+
+message WalkReposRequest {
+ string storage_name = 1 [(storage)=true];
+}
+
+message WalkReposResponse {
+ string relative_path = 1;
+}
diff --git a/ruby/proto/gitaly.rb b/ruby/proto/gitaly.rb
index e130336b0..8674ab879 100644
--- a/ruby/proto/gitaly.rb
+++ b/ruby/proto/gitaly.rb
@@ -15,6 +15,8 @@ require 'gitaly/diff_services_pb'
require 'gitaly/hook_services_pb'
+require 'gitaly/internal_services_pb'
+
require 'gitaly/namespace_services_pb'
require 'gitaly/objectpool_services_pb'
diff --git a/ruby/proto/gitaly/internal_pb.rb b/ruby/proto/gitaly/internal_pb.rb
new file mode 100644
index 000000000..2e2b6a1b6
--- /dev/null
+++ b/ruby/proto/gitaly/internal_pb.rb
@@ -0,0 +1,19 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: internal.proto
+
+require 'google/protobuf'
+
+require 'shared_pb'
+Google::Protobuf::DescriptorPool.generated_pool.build do
+ add_message "gitaly.WalkReposRequest" do
+ optional :storage_name, :string, 1
+ end
+ add_message "gitaly.WalkReposResponse" do
+ optional :relative_path, :string, 1
+ end
+end
+
+module Gitaly
+ WalkReposRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("gitaly.WalkReposRequest").msgclass
+ WalkReposResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("gitaly.WalkReposResponse").msgclass
+end
diff --git a/ruby/proto/gitaly/internal_services_pb.rb b/ruby/proto/gitaly/internal_services_pb.rb
new file mode 100644
index 000000000..8f403d436
--- /dev/null
+++ b/ruby/proto/gitaly/internal_services_pb.rb
@@ -0,0 +1,26 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# Source: internal.proto for package 'gitaly'
+
+require 'grpc'
+require 'internal_pb'
+
+module Gitaly
+ module InternalGitaly
+ # InternalGitaly is a gRPC service meant to be served by a Gitaly node, but
+ # only reachable by Praefect or other Gitalies
+ class Service
+
+ include GRPC::GenericService
+
+ self.marshal_class_method = :encode
+ self.unmarshal_class_method = :decode
+ self.service_name = 'gitaly.InternalGitaly'
+
+ # WalkRepos walks the storage and streams back all known git repos on the
+ # requested storage
+ rpc :WalkRepos, WalkReposRequest, stream(WalkReposResponse)
+ end
+
+ Stub = Service.rpc_stub_class
+ end
+end