From 09fddb429be9fbc483056a5538c5ae82befdc6c8 Mon Sep 17 00:00:00 2001 From: jramsay Date: Mon, 14 Oct 2019 11:30:15 -0700 Subject: [PATCH 1/2] Add Hook rpc method implementations --- cmd/gitaly-hooks/hooks.go | 178 +++++++++++++++++--- cmd/gitaly-hooks/hooks_test.go | 96 +++++++++-- go.sum | 1 + internal/git/receivepack.go | 7 +- internal/service/hooks/post_receive.go | 81 +++++++++ internal/service/hooks/post_receive_test.go | 28 +++ internal/service/hooks/pre_receive.go | 81 +++++++++ internal/service/hooks/pre_receive_test.go | 28 +++ internal/service/hooks/server.go | 10 ++ internal/service/hooks/stream_command.go | 54 ++++++ internal/service/hooks/testhelper_test.go | 47 ++++++ internal/service/hooks/update.go | 67 ++++++++ internal/service/hooks/update_test.go | 27 +++ internal/service/register.go | 2 + ruby/gitlab-shell/hooks/post-receive | 2 +- ruby/gitlab-shell/hooks/pre-receive | 2 +- ruby/gitlab-shell/hooks/update | 2 +- 17 files changed, 678 insertions(+), 35 deletions(-) create mode 100644 internal/service/hooks/post_receive.go create mode 100644 internal/service/hooks/post_receive_test.go create mode 100644 internal/service/hooks/pre_receive.go create mode 100644 internal/service/hooks/pre_receive_test.go create mode 100644 internal/service/hooks/server.go create mode 100644 internal/service/hooks/stream_command.go create mode 100644 internal/service/hooks/testhelper_test.go create mode 100644 internal/service/hooks/update.go create mode 100644 internal/service/hooks/update_test.go diff --git a/cmd/gitaly-hooks/hooks.go b/cmd/gitaly-hooks/hooks.go index c526c103365..0076551d5f0 100644 --- a/cmd/gitaly-hooks/hooks.go +++ b/cmd/gitaly-hooks/hooks.go @@ -4,22 +4,27 @@ import ( "context" "errors" "fmt" + "io" "net/http" "os" - "os/exec" - "path/filepath" "strings" - "gitlab.com/gitlab-org/gitaly/internal/command" + "gitlab.com/gitlab-org/gitaly/streamio" + + "gitlab.com/gitlab-org/gitaly/client" "gitlab.com/gitlab-org/gitaly/internal/log" + "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" + "google.golang.org/grpc" "gopkg.in/yaml.v2" + + gitalyauth "gitlab.com/gitlab-org/gitaly/auth" ) func main() { var logger = log.NewHookLogger() if len(os.Args) < 2 { - logger.Fatal(errors.New("requires hook name")) + logger.Fatal(errors.New("requires subcommand")) } subCmd := os.Args[1] @@ -36,17 +41,17 @@ func main() { os.Exit(0) } - gitlabRubyDir := os.Getenv("GITALY_RUBY_DIR") - if gitlabRubyDir == "" { - logger.Fatal(errors.New("GITALY_RUBY_DIR not set")) - } - - rubyHookPath := filepath.Join(gitlabRubyDir, "gitlab-shell", "hooks", subCmd) - ctx, cancel := context.WithCancel(context.Background()) defer cancel() - var hookCmd *exec.Cmd + conn, err := client.Dial(os.Getenv("GITALY_SOCKET"), dialOpts()) + if err != nil { + logger.Fatalf("error when dialing: %v", err) + } + + c := gitalypb.NewHookServiceClient(conn) + + var hookSuccess bool switch subCmd { case "update": @@ -54,23 +59,154 @@ func main() { if len(args) != 3 { logger.Fatal(errors.New("update hook missing required arguments")) } + ref, oldValue, newValue := args[0], args[1], args[2] + + req := &gitalypb.UpdateHookRequest{ + Repository: &gitalypb.Repository{ + StorageName: os.Getenv("GL_REPO_STORAGE"), + RelativePath: os.Getenv("GL_REPO_RELATIVE_PATH"), + GlRepository: os.Getenv("GL_REPOSITORY"), + }, + KeyId: os.Getenv("GL_ID"), + Ref: []byte(ref), + OldValue: oldValue, + NewValue: newValue, + } - hookCmd = exec.Command(rubyHookPath, args...) - case "pre-receive", "post-receive": - hookCmd = exec.Command(rubyHookPath) + stream, err := c.UpdateHook(ctx, req) + if err != nil { + logger.Fatalf("error when starting command for %v: %v", subCmd, err) + } + + if hookSuccess, err = recvHookResponse(stream, new(gitalypb.UpdateHookResponse), os.Stdout, os.Stderr); err != nil { + logger.Fatalf("error when receiving data for %v: %v", subCmd, err) + } + case "pre-receive": + stream, err := c.PreReceiveHook(ctx) + if err != nil { + logger.Fatalf("error when getting stream client: %v", err) + } + if err = sendRequest(stream, preReceiveHookRequest, os.Stdin); err != nil { + logger.Fatalf("error when sending data for %v: %v", subCmd, err) + } + if err = stream.CloseSend(); err != nil { + logger.Fatalf("error when closing sending stream for %v: %v", subCmd, err) + } + + if hookSuccess, err = recvHookResponse(stream, new(gitalypb.PreReceiveHookResponse), os.Stdout, os.Stderr); err != nil { + logger.Fatalf("error when receiving data for %v: %v", subCmd, err) + } + case "post-receive": + stream, err := c.PostReceiveHook(ctx) + if err != nil { + logger.Fatalf("error when getting stream client: %v", err) + } + + if err = sendRequest(stream, postReceiveHookRequest, os.Stdin); err != nil { + logger.Fatalf("error when sending data for %v: %v", subCmd, err) + } + if err = stream.CloseSend(); err != nil { + logger.Fatalf("error when closing sending stream for %v: %v", subCmd, err) + } + + if hookSuccess, err = recvHookResponse(stream, new(gitalypb.PostReceiveHookResponse), os.Stdout, os.Stderr); err != nil { + logger.Fatalf("error when receiving data for %v: %v", subCmd, err) + } default: - logger.Fatal(errors.New("hook name invalid")) + logger.Fatal(errors.New("subcommand name invalid")) } - cmd, err := command.New(ctx, hookCmd, os.Stdin, os.Stdout, os.Stderr, os.Environ()...) - if err != nil { - logger.Fatalf("error when starting command for %v: %v", rubyHookPath, err) + if !hookSuccess { + os.Exit(1) } - if err = cmd.Wait(); err != nil { - os.Exit(1) + os.Exit(0) +} + +// streamRequestGenerator is a function that generates requests for sending to a stream +type streamRequestGenerator func(firstRequest bool, p []byte) interface{} + +// SendRequest streams requests using a reader +func sendRequest(stream grpc.ClientStream, g streamRequestGenerator, r io.Reader) error { + if err := stream.SendMsg(g(true, nil)); err != nil { + return err + } + + w := streamio.NewWriter(func(p []byte) error { + return stream.SendMsg(g(false, p)) + }) + + if _, err := io.Copy(w, r); err != nil { + return err + } + + return stream.CloseSend() +} + +func postReceiveHookRequest(first bool, p []byte) interface{} { + if first { + return &gitalypb.PostReceiveHookRequest{ + Repository: &gitalypb.Repository{ + StorageName: os.Getenv("GL_REPO_STORAGE"), + RelativePath: os.Getenv("GL_REPO_RELATIVE_PATH"), + GlRepository: os.Getenv("GL_REPOSITORY"), + }, + KeyId: os.Getenv("GL_ID"), + } + } + + return &gitalypb.PostReceiveHookRequest{Stdin: p} +} + +func preReceiveHookRequest(first bool, p []byte) interface{} { + if first { + return &gitalypb.PreReceiveHookRequest{ + Repository: &gitalypb.Repository{ + StorageName: os.Getenv("GL_REPO_STORAGE"), + RelativePath: os.Getenv("GL_REPO_RELATIVE_PATH"), + GlRepository: os.Getenv("GL_REPOSITORY"), + }, + KeyId: os.Getenv("GL_ID"), + Protocol: os.Getenv("GL_PROTOCOL"), + } } + return &gitalypb.PreReceiveHookRequest{Stdin: p} +} + +type hookResponse interface { + GetStdout() []byte + GetStderr() []byte + GetSuccess() bool +} + +func recvHookResponse(stream grpc.ClientStream, resp hookResponse, stdout, stderr io.Writer) (bool, error) { + var err error + var success bool + for { + err = stream.RecvMsg(resp) + if err != nil { + break + } + + if _, err = stdout.Write(resp.GetStdout()); err != nil { + return false, err + } + if _, err = stderr.Write(resp.GetStderr()); err != nil { + return false, err + } + success = resp.GetSuccess() + } + + if err != io.EOF { + return false, err + } + + return success, nil +} + +func dialOpts() []grpc.DialOption { + return append(client.DefaultDialOpts, grpc.WithPerRPCCredentials(gitalyauth.RPCCredentials(os.Getenv("GITALY_TOKEN")))) } // GitlabShellConfig contains a subset of gitlabshell's config.yml diff --git a/cmd/gitaly-hooks/hooks_test.go b/cmd/gitaly-hooks/hooks_test.go index c7dbae128ff..0e6913d1d09 100644 --- a/cmd/gitaly-hooks/hooks_test.go +++ b/cmd/gitaly-hooks/hooks_test.go @@ -7,6 +7,7 @@ import ( "fmt" "io/ioutil" "log" + "net" "net/http" "net/http/httptest" "os" @@ -21,7 +22,10 @@ import ( "gitlab.com/gitlab-org/gitaly/internal/command" "gitlab.com/gitlab-org/gitaly/internal/config" "gitlab.com/gitlab-org/gitaly/internal/testhelper" + "google.golang.org/grpc" "gopkg.in/yaml.v2" + + serverPkg "gitlab.com/gitlab-org/gitaly/internal/server" ) func TestMain(m *testing.M) { @@ -41,19 +45,35 @@ func TestHooksPrePostReceive(t *testing.T) { key := 1234 glRepository := "some_repo" + testRepo, _, cleanupFn := testhelper.NewTestRepo(t) + defer cleanupFn() + tempGitlabShellDir, cleanup := createTempGitlabShellDir(t) defer cleanup() changes := "abc" + gitlabShellDir := config.Config.GitlabShell.Dir + defer func() { + config.Config.GitlabShell.Dir = gitlabShellDir + }() + + config.Config.GitlabShell.Dir = tempGitlabShellDir + ts := gitlabTestServer(t, "", "", secretToken, key, glRepository, changes, true) defer ts.Close() writeTemporaryConfigFile(t, tempGitlabShellDir, GitlabShellConfig{GitlabURL: ts.URL}) + srv, socket := runFullServer(t) + defer srv.Stop() + writeShellSecretFile(t, tempGitlabShellDir, secretToken) for _, hook := range []string{"pre-receive", "post-receive"} { - for envName, env := range map[string][]string{"new": env(t, glRepository, tempGitlabShellDir, key), "old": oldEnv(t, glRepository, tempGitlabShellDir, key)} { + for envName, env := range map[string][]string{ + "new": env(t, glRepository, tempGitlabShellDir, testRepo.GetStorageName(), testRepo.GetRelativePath(), socket, key), + "old": oldEnv(t, glRepository, tempGitlabShellDir, key), + } { t.Run(hook+"."+envName, func(t *testing.T) { var stderr, stdout bytes.Buffer stdin := bytes.NewBuffer([]byte(changes)) @@ -79,12 +99,27 @@ func TestHooksUpdate(t *testing.T) { defer cleanup() writeTemporaryConfigFile(t, tempGitlabShellDir, GitlabShellConfig{GitlabURL: "http://www.example.com"}) + testRepo, _, cleanupFn := testhelper.NewTestRepo(t) + defer cleanupFn() + writeShellSecretFile(t, tempGitlabShellDir, "the wrong token") + gitlabShellDir := config.Config.GitlabShell.Dir + defer func() { + config.Config.GitlabShell.Dir = gitlabShellDir + }() + + config.Config.GitlabShell.Dir = tempGitlabShellDir + + srv, socket := runFullServer(t) + defer srv.Stop() + require.NoError(t, os.MkdirAll(filepath.Join(tempGitlabShellDir, "hooks", "update.d"), 0755)) testhelper.MustRunCommand(t, nil, "cp", "testdata/update", filepath.Join(tempGitlabShellDir, "hooks", "update.d", "update")) - for envName, env := range map[string][]string{"new": env(t, glRepository, tempGitlabShellDir, key), "old": oldEnv(t, glRepository, tempGitlabShellDir, key)} { + for envName, env := range map[string][]string{ + "new": env(t, glRepository, tempGitlabShellDir, testRepo.GetStorageName(), testRepo.GetRelativePath(), socket, key), + "old": oldEnv(t, glRepository, tempGitlabShellDir, key)} { t.Run(envName, func(t *testing.T) { refval, oldval, newval := "refval", "oldval", "newval" var stdout, stderr bytes.Buffer @@ -118,6 +153,9 @@ func TestHooksPostReceiveFailed(t *testing.T) { tempGitlabShellDir, cleanup := createTempGitlabShellDir(t) defer cleanup() + testRepo, _, cleanupFn := testhelper.NewTestRepo(t) + defer cleanupFn() + // By setting the last parameter to false, the post-receive API call will // send back {"reference_counter_increased": false}, indicating something went wrong // with the call @@ -128,7 +166,19 @@ func TestHooksPostReceiveFailed(t *testing.T) { writeTemporaryConfigFile(t, tempGitlabShellDir, GitlabShellConfig{GitlabURL: ts.URL}) writeShellSecretFile(t, tempGitlabShellDir, secretToken) - for envName, env := range map[string][]string{"new": env(t, glRepository, tempGitlabShellDir, key), "old": oldEnv(t, glRepository, tempGitlabShellDir, key)} { + gitlabShellDir := config.Config.GitlabShell.Dir + defer func() { + config.Config.GitlabShell.Dir = gitlabShellDir + }() + + config.Config.GitlabShell.Dir = tempGitlabShellDir + + srv, socket := runFullServer(t) + defer srv.Stop() + + for envName, env := range map[string][]string{ + "new": env(t, glRepository, tempGitlabShellDir, testRepo.GetStorageName(), testRepo.GetRelativePath(), socket, key), + "old": oldEnv(t, glRepository, tempGitlabShellDir, key)} { t.Run(envName, func(t *testing.T) { var stdout, stderr bytes.Buffer @@ -157,12 +207,26 @@ func TestHooksNotAllowed(t *testing.T) { defer cleanup() ts := gitlabTestServer(t, "", "", secretToken, key, glRepository, "", true) + testRepo, _, cleanupFn := testhelper.NewTestRepo(t) + defer cleanupFn() + defer ts.Close() writeTemporaryConfigFile(t, tempGitlabShellDir, GitlabShellConfig{GitlabURL: ts.URL}) writeShellSecretFile(t, tempGitlabShellDir, "the wrong token") - for envName, env := range map[string][]string{"new": env(t, glRepository, tempGitlabShellDir, key), "old": oldEnv(t, glRepository, tempGitlabShellDir, key)} { + gitlabShellDir := config.Config.GitlabShell.Dir + defer func() { + config.Config.GitlabShell.Dir = gitlabShellDir + }() + + config.Config.GitlabShell.Dir = tempGitlabShellDir + srv, socket := runFullServer(t) + defer srv.Stop() + + for envName, env := range map[string][]string{ + "new": env(t, glRepository, tempGitlabShellDir, testRepo.GetStorageName(), testRepo.GetRelativePath(), socket, key), + "old": oldEnv(t, glRepository, tempGitlabShellDir, key)} { t.Run(envName, func(t *testing.T) { var stderr, stdout bytes.Buffer @@ -228,6 +292,20 @@ func TestCheckBadCreds(t *testing.T) { require.Empty(t, stdout.String()) } +func runFullServer(t *testing.T) (*grpc.Server, string) { + server := serverPkg.NewInsecure(nil) + serverSocketPath := testhelper.GetTemporaryGitalySocketFileName() + + listener, err := net.Listen("unix", serverSocketPath) + if err != nil { + t.Fatal(err) + } + + go server.Serve(listener) + + return server, "unix://" + serverSocketPath +} + func handleAllowed(t *testing.T, secretToken string, key int, glRepository, changes string) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { require.NoError(t, r.ParseForm()) @@ -332,13 +410,12 @@ func writeTemporaryConfigFile(t *testing.T, dir string, config GitlabShellConfig return path } -func env(t *testing.T, glRepo, gitlabShellDir string, key int) []string { - rubyDir, err := filepath.Abs("../../ruby") - require.NoError(t, err) - +func env(t *testing.T, glRepo, gitlabShellDir, glStorage, glRelativePath, gitalySocket string, key int) []string { return append(oldEnv(t, glRepo, gitlabShellDir, key), []string{ "GITALY_BIN_DIR=testdata/gitaly-libexec", - fmt.Sprintf("GITALY_RUBY_DIR=%s", rubyDir), + fmt.Sprintf("GL_REPO_STORAGE=%s", glStorage), + fmt.Sprintf("GL_REPO_RELATIVE_PATH=%s", glRelativePath), + fmt.Sprintf("GITALY_SOCKET=%s", gitalySocket), }...) } @@ -351,7 +428,6 @@ func oldEnv(t *testing.T, glRepo, gitlabShellDir string, key int) []string { fmt.Sprintf("GITALY_LOG_DIR=%s", gitlabShellDir), "GITALY_LOG_LEVEL=info", "GITALY_LOG_FORMAT=json", - fmt.Sprintf("GITALY_LOG_DIR=%s", gitlabShellDir), }, os.Environ()...) } diff --git a/go.sum b/go.sum index aa4208afbbd..3bcd2eda843 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,7 @@ github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nI github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gavv/monotime v0.0.0-20190418164738-30dba4353424/go.mod h1:vmp8DIyckQMXOPl0AQVHt+7n5h7Gb7hS6CUydiV8QeA= +github.com/getsentry/raven-go v0.1.0 h1:lc5jnN9D+q3panDpihwShgaOVvP6esoMEKbID2yhLoQ= github.com/getsentry/raven-go v0.1.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/getsentry/sentry-go v0.3.0 h1:6E+Oxq9CbT1kQrBPJ/RmWPqFBVS4CqU25RaMqeKnbs8= github.com/getsentry/sentry-go v0.3.0/go.mod h1:Mrvr9TRhClLixedDiyFeucydQGOv4o7YQcW+Ry5vDdU= diff --git a/internal/git/receivepack.go b/internal/git/receivepack.go index 65d76457383..53d7ef9b736 100644 --- a/internal/git/receivepack.go +++ b/internal/git/receivepack.go @@ -6,6 +6,7 @@ import ( "gitlab.com/gitlab-org/gitaly/internal/config" "gitlab.com/gitlab-org/gitaly/internal/git/hooks" "gitlab.com/gitlab-org/gitaly/internal/gitlabshell" + "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" ) // ReceivePackRequest abstracts away the different requests that end up @@ -14,6 +15,7 @@ type ReceivePackRequest interface { GetGlId() string GetGlUsername() string GetGlRepository() string + GetRepository() *gitalypb.Repository } // HookEnv is information we pass down to the Git hooks during @@ -23,7 +25,10 @@ func HookEnv(req ReceivePackRequest) []string { fmt.Sprintf("GL_ID=%s", req.GetGlId()), fmt.Sprintf("GL_USERNAME=%s", req.GetGlUsername()), fmt.Sprintf("GL_REPOSITORY=%s", req.GetGlRepository()), - fmt.Sprintf("GITLAB_SHELL_DIR=%s", config.Config.GitlabShell.Dir), + fmt.Sprintf("GL_REPO_STORAGE=%s", req.GetRepository().GetStorageName()), + fmt.Sprintf("GL_REPO_RELATIVE_PATH=%s", req.GetRepository().GetRelativePath()), + fmt.Sprintf("GITALY_SOCKET=%s", config.GitalyInternalSocketPath()), + fmt.Sprintf("GITALY_TOKEN=%s", config.Config.Auth.Token), }, gitlabshell.Env()...) } diff --git a/internal/service/hooks/post_receive.go b/internal/service/hooks/post_receive.go new file mode 100644 index 00000000000..9d372c43406 --- /dev/null +++ b/internal/service/hooks/post_receive.go @@ -0,0 +1,81 @@ +package hook + +import ( + "errors" + "fmt" + "os/exec" + "path/filepath" + + "gitlab.com/gitlab-org/gitaly/streamio" + + "gitlab.com/gitlab-org/gitaly/internal/config" + "gitlab.com/gitlab-org/gitaly/internal/gitlabshell" + "gitlab.com/gitlab-org/gitaly/internal/helper" + "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" +) + +func postReceiveStdout(p []byte) interface{} { + return &gitalypb.PostReceiveHookResponse{Stdout: p} +} + +func postReceiveStderr(p []byte) interface{} { + return &gitalypb.PostReceiveHookResponse{Stderr: p} +} + +func postReceiveLastResponse(success bool) interface{} { + return &gitalypb.PostReceiveHookResponse{Success: success} +} + +func (s *server) PostReceiveHook(stream gitalypb.HookService_PostReceiveHookServer) error { + firstRequest, err := stream.Recv() + if err != nil { + return helper.ErrInternal(err) + } + + if err := validatePostReceiveHookRequest(firstRequest); err != nil { + return helper.ErrInvalidArgument(err) + } + + ctx := stream.Context() + + postReceiveHookPath := filepath.Join(config.Config.Ruby.Dir, "gitlab-shell", "hooks", "post-receive") + + repoPath, err := helper.GetRepoPath(firstRequest.GetRepository()) + if err != nil { + return helper.ErrInternal(err) + } + + env := append(gitlabshell.Env(), + fmt.Sprintf("GL_REPO_PATH=%s", repoPath), + fmt.Sprintf("GL_ID=%s", firstRequest.GetKeyId()), + fmt.Sprintf("GL_REPOSITORY=%s", firstRequest.GetRepository().GetGlRepository()), + ) + + stdin := streamio.NewReader(func() ([]byte, error) { + req, err := stream.Recv() + return req.GetStdin(), err + }) + + if err := streamCommandResponse( + ctx, + stream, + exec.Command(postReceiveHookPath), + stdin, + postReceiveStdout, + postReceiveStderr, + postReceiveLastResponse, + env, + ); err != nil { + return helper.ErrInternal(err) + } + + return nil +} + +func validatePostReceiveHookRequest(in *gitalypb.PostReceiveHookRequest) error { + if in.GetRepository() == nil { + return errors.New("repository is empty") + } + + return nil +} diff --git a/internal/service/hooks/post_receive_test.go b/internal/service/hooks/post_receive_test.go new file mode 100644 index 00000000000..3bac6b734fd --- /dev/null +++ b/internal/service/hooks/post_receive_test.go @@ -0,0 +1,28 @@ +package hook + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "gitlab.com/gitlab-org/gitaly/internal/testhelper" + "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" + "google.golang.org/grpc/codes" +) + +func TestPostReceiveInvalidArgument(t *testing.T) { + server, serverSocketPath := runHooksServer(t) + defer server.Stop() + + client, conn := newHooksClient(t, serverSocketPath) + defer conn.Close() + + ctx, cancel := testhelper.Context() + defer cancel() + + stream, err := client.PostReceiveHook(ctx) + require.NoError(t, err) + require.NoError(t, stream.Send(&gitalypb.PostReceiveHookRequest{})) + _, err = stream.Recv() + testhelper.RequireGrpcError(t, err, codes.InvalidArgument) +} diff --git a/internal/service/hooks/pre_receive.go b/internal/service/hooks/pre_receive.go new file mode 100644 index 00000000000..a88143ecbb8 --- /dev/null +++ b/internal/service/hooks/pre_receive.go @@ -0,0 +1,81 @@ +package hook + +import ( + "errors" + "fmt" + "os/exec" + "path/filepath" + + "gitlab.com/gitlab-org/gitaly/streamio" + + "gitlab.com/gitlab-org/gitaly/internal/config" + "gitlab.com/gitlab-org/gitaly/internal/gitlabshell" + "gitlab.com/gitlab-org/gitaly/internal/helper" + "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" +) + +func preReceiveStdout(p []byte) interface{} { + return &gitalypb.PreReceiveHookResponse{Stdout: p} +} + +func preReceiveStderr(p []byte) interface{} { + return &gitalypb.PreReceiveHookResponse{Stderr: p} +} + +func preReceiveLastResponse(success bool) interface{} { + return &gitalypb.PreReceiveHookResponse{Success: success} +} + +func (s *server) PreReceiveHook(stream gitalypb.HookService_PreReceiveHookServer) error { + firstRequest, err := stream.Recv() + if err != nil { + return helper.ErrInternal(err) + } + + if err := validatePreReceiveHookRequest(firstRequest); err != nil { + return helper.ErrInvalidArgument(err) + } + ctx := stream.Context() + + preReceiveHookPath := filepath.Join(config.Config.Ruby.Dir, "gitlab-shell", "hooks", "pre-receive") + + repoPath, err := helper.GetRepoPath(firstRequest.GetRepository()) + if err != nil { + return helper.ErrInternal(err) + } + + env := append(gitlabshell.Env(), + fmt.Sprintf("GL_ID=%s", firstRequest.GetKeyId()), + fmt.Sprintf("GL_PROTOCOL=%s", firstRequest.GetProtocol()), + fmt.Sprintf("GL_REPO_PATH=%s", repoPath), + fmt.Sprintf("GL_REPOSITORY=%s", firstRequest.GetRepository().GetGlRepository()), + ) + + stdin := streamio.NewReader(func() ([]byte, error) { + req, err := stream.Recv() + return req.GetStdin(), err + }) + + if err := streamCommandResponse( + ctx, + stream, + exec.Command(preReceiveHookPath), + stdin, + preReceiveStdout, + preReceiveStderr, + preReceiveLastResponse, + env, + ); err != nil { + return helper.ErrInternal(err) + } + + return nil +} + +func validatePreReceiveHookRequest(in *gitalypb.PreReceiveHookRequest) error { + if in.GetRepository() == nil { + return errors.New("repository is empty") + } + + return nil +} diff --git a/internal/service/hooks/pre_receive_test.go b/internal/service/hooks/pre_receive_test.go new file mode 100644 index 00000000000..671c34fecad --- /dev/null +++ b/internal/service/hooks/pre_receive_test.go @@ -0,0 +1,28 @@ +package hook + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "gitlab.com/gitlab-org/gitaly/internal/testhelper" + "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" + "google.golang.org/grpc/codes" +) + +func TestPreReceiveInvalidArgument(t *testing.T) { + server, serverSocketPath := runHooksServer(t) + defer server.Stop() + + client, conn := newHooksClient(t, serverSocketPath) + defer conn.Close() + + ctx, cancel := testhelper.Context() + defer cancel() + + stream, err := client.PreReceiveHook(ctx) + require.NoError(t, err) + require.NoError(t, stream.Send(&gitalypb.PreReceiveHookRequest{})) + _, err = stream.Recv() + testhelper.RequireGrpcError(t, err, codes.InvalidArgument) +} diff --git a/internal/service/hooks/server.go b/internal/service/hooks/server.go new file mode 100644 index 00000000000..1977af1df5b --- /dev/null +++ b/internal/service/hooks/server.go @@ -0,0 +1,10 @@ +package hook + +import "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" + +type server struct{} + +// NewServer creates a new instance of a gRPC namespace server +func NewServer() gitalypb.HookServiceServer { + return &server{} +} diff --git a/internal/service/hooks/stream_command.go b/internal/service/hooks/stream_command.go new file mode 100644 index 00000000000..79cd2e5d926 --- /dev/null +++ b/internal/service/hooks/stream_command.go @@ -0,0 +1,54 @@ +package hook + +import ( + "context" + "io" + "os/exec" + + grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus" + "gitlab.com/gitlab-org/gitaly/internal/command" + "gitlab.com/gitlab-org/gitaly/internal/helper" + "gitlab.com/gitlab-org/gitaly/streamio" + "google.golang.org/grpc" +) + +// streamResponseGenerator is a function that generates responses for sending to a stream +type streamResponseGenerator func(p []byte) interface{} + +type streamLastResponseGenerator func(success bool) interface{} + +func streamCommandResponse( + ctx context.Context, + stream grpc.ServerStream, + c *exec.Cmd, + stdin io.Reader, + stdoutGen streamResponseGenerator, + stderrGen streamResponseGenerator, + lastRespGen streamLastResponseGenerator, + env []string, +) error { + stdout := streamio.NewWriter(func(p []byte) error { + return stream.SendMsg(stdoutGen(p)) + }) + stderr := streamio.NewWriter(func(p []byte) error { + return stream.SendMsg(stderrGen(p)) + }) + cmd, err := command.New(ctx, c, stdin, stdout, stderr, env...) + if err != nil { + return helper.ErrInternal(err) + } + + success := true + + // handle an error from the ruby hook by setting success = false + if err = cmd.Wait(); err != nil { + grpc_logrus.Extract(ctx).WithError(err).Error("failed to run git hook") + success = false + } + + if err := stream.SendMsg(lastRespGen(success)); err != nil { + return helper.ErrInternal(err) + } + + return nil +} diff --git a/internal/service/hooks/testhelper_test.go b/internal/service/hooks/testhelper_test.go new file mode 100644 index 00000000000..72367ac7554 --- /dev/null +++ b/internal/service/hooks/testhelper_test.go @@ -0,0 +1,47 @@ +package hook + +import ( + "net" + "testing" + + gitalyauth "gitlab.com/gitlab-org/gitaly/auth" + "gitlab.com/gitlab-org/gitaly/internal/config" + "gitlab.com/gitlab-org/gitaly/internal/server/auth" + "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 newHooksClient(t *testing.T, serverSocketPath string) (gitalypb.HookServiceClient, *grpc.ClientConn) { + connOpts := []grpc.DialOption{ + grpc.WithInsecure(), + grpc.WithPerRPCCredentials(gitalyauth.RPCCredentials(config.Config.Auth.Token)), + } + conn, err := grpc.Dial(serverSocketPath, connOpts...) + if err != nil { + t.Fatal(err) + } + + return gitalypb.NewHookServiceClient(conn), conn +} + +func runHooksServer(t *testing.T) (*grpc.Server, string) { + streamInt := []grpc.StreamServerInterceptor{auth.StreamServerInterceptor(config.Config.Auth)} + unaryInt := []grpc.UnaryServerInterceptor{auth.UnaryServerInterceptor(config.Config.Auth)} + + server := testhelper.NewTestGrpcServer(t, streamInt, unaryInt) + serverSocketPath := testhelper.GetTemporaryGitalySocketFileName() + + listener, err := net.Listen("unix", serverSocketPath) + if err != nil { + t.Fatal(err) + } + + gitalypb.RegisterHookServiceServer(server, NewServer()) + reflection.Register(server) + + go server.Serve(listener) + + return server, "unix://" + serverSocketPath +} diff --git a/internal/service/hooks/update.go b/internal/service/hooks/update.go new file mode 100644 index 00000000000..10c0d90c179 --- /dev/null +++ b/internal/service/hooks/update.go @@ -0,0 +1,67 @@ +package hook + +import ( + "errors" + "fmt" + "os/exec" + "path/filepath" + + "gitlab.com/gitlab-org/gitaly/internal/config" + "gitlab.com/gitlab-org/gitaly/internal/gitlabshell" + "gitlab.com/gitlab-org/gitaly/internal/helper" + "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" +) + +func updateHookStdout(p []byte) interface{} { + return &gitalypb.UpdateHookResponse{Stdout: p} +} + +func updateHookStderr(p []byte) interface{} { + return &gitalypb.UpdateHookResponse{Stderr: p} +} + +func updateHookLastResponse(success bool) interface{} { + return &gitalypb.UpdateHookResponse{Success: success} +} + +func (s *server) UpdateHook(in *gitalypb.UpdateHookRequest, stream gitalypb.HookService_UpdateHookServer) error { + if err := validateUpdateHookRequest(in); err != nil { + return helper.ErrInvalidArgument(err) + } + ctx := stream.Context() + + updateHookPath := filepath.Join(config.Config.Ruby.Dir, "gitlab-shell", "hooks", "update") + + repoPath, err := helper.GetRepoPath(in.GetRepository()) + if err != nil { + return helper.ErrInternal(err) + } + + env := append(gitlabshell.Env(), []string{ + fmt.Sprintf("GL_ID=%s", in.GetKeyId()), + fmt.Sprintf("GL_REPO_PATH=%s", repoPath), + }...) + + if err := streamCommandResponse( + ctx, + stream, + exec.Command(updateHookPath, string(in.GetRef()), in.GetOldValue(), in.GetNewValue()), + nil, + updateHookStdout, + updateHookStderr, + updateHookLastResponse, + env, + ); err != nil { + return helper.ErrInternal(err) + } + + return nil +} + +func validateUpdateHookRequest(in *gitalypb.UpdateHookRequest) error { + if in.GetRepository() == nil { + return errors.New("repository is empty") + } + + return nil +} diff --git a/internal/service/hooks/update_test.go b/internal/service/hooks/update_test.go new file mode 100644 index 00000000000..35faae5b3b2 --- /dev/null +++ b/internal/service/hooks/update_test.go @@ -0,0 +1,27 @@ +package hook + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "gitlab.com/gitlab-org/gitaly/internal/testhelper" + "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" + "google.golang.org/grpc/codes" +) + +func TestUpdateInvalidArgument(t *testing.T) { + server, serverSocketPath := runHooksServer(t) + defer server.Stop() + + client, conn := newHooksClient(t, serverSocketPath) + defer conn.Close() + + ctx, cancel := testhelper.Context() + defer cancel() + + stream, err := client.UpdateHook(ctx, &gitalypb.UpdateHookRequest{}) + require.NoError(t, err) + _, err = stream.Recv() + testhelper.RequireGrpcError(t, err, codes.InvalidArgument) +} diff --git a/internal/service/register.go b/internal/service/register.go index f12215e56e8..317e0a2a3d8 100644 --- a/internal/service/register.go +++ b/internal/service/register.go @@ -7,6 +7,7 @@ import ( "gitlab.com/gitlab-org/gitaly/internal/service/commit" "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/namespace" "gitlab.com/gitlab-org/gitaly/internal/service/objectpool" "gitlab.com/gitlab-org/gitaly/internal/service/operations" @@ -41,6 +42,7 @@ func RegisterAll(grpcServer *grpc.Server, rubyServer *rubyserver.Server) { gitalypb.RegisterRemoteServiceServer(grpcServer, remote.NewServer(rubyServer)) gitalypb.RegisterServerServiceServer(grpcServer, server.NewServer()) gitalypb.RegisterObjectPoolServiceServer(grpcServer, objectpool.NewServer()) + gitalypb.RegisterHookServiceServer(grpcServer, hook.NewServer()) healthpb.RegisterHealthServer(grpcServer, health.NewServer()) } diff --git a/ruby/gitlab-shell/hooks/post-receive b/ruby/gitlab-shell/hooks/post-receive index 2b6538f0323..6669285ceed 100755 --- a/ruby/gitlab-shell/hooks/post-receive +++ b/ruby/gitlab-shell/hooks/post-receive @@ -6,7 +6,7 @@ refs = $stdin.read key_id = ENV.delete('GL_ID') gl_repository = ENV['GL_REPOSITORY'] -repo_path = Dir.pwd +repo_path = ENV['GL_REPO_PATH'] || Dir.pwd require_relative '../lib/gitlab_custom_hook' require_relative '../lib/hooks_utils' diff --git a/ruby/gitlab-shell/hooks/pre-receive b/ruby/gitlab-shell/hooks/pre-receive index 6ce58795195..2cab1a5e9bf 100755 --- a/ruby/gitlab-shell/hooks/pre-receive +++ b/ruby/gitlab-shell/hooks/pre-receive @@ -6,7 +6,7 @@ refs = $stdin.read key_id = ENV.delete('GL_ID') protocol = ENV.delete('GL_PROTOCOL') -repo_path = Dir.pwd +repo_path = ENV['GL_REPO_PATH'] || Dir.pwd gl_repository = ENV['GL_REPOSITORY'] def increase_reference_counter(gl_repository, repo_path) diff --git a/ruby/gitlab-shell/hooks/update b/ruby/gitlab-shell/hooks/update index 4c2fc08b0d7..dd6eab724d2 100755 --- a/ruby/gitlab-shell/hooks/update +++ b/ruby/gitlab-shell/hooks/update @@ -6,7 +6,7 @@ ref_name = ARGV[0] old_value = ARGV[1] new_value = ARGV[2] -repo_path = Dir.pwd +repo_path = ENV['GL_REPO_PATH'] || Dir.pwd key_id = ENV.delete('GL_ID') require_relative '../lib/gitlab_custom_hook' -- GitLab From 60ed9bc9eedca979234992045ca7546b18000366 Mon Sep 17 00:00:00 2001 From: John Cai Date: Fri, 6 Dec 2019 10:06:56 -0800 Subject: [PATCH 2/2] Change hook rpcs to streaming --- proto/go/gitalypb/hook.pb.go | 296 +++++++++++++++++--------- proto/hook.proto | 6 +- ruby/proto/gitaly/hook_services_pb.rb | 6 +- 3 files changed, 200 insertions(+), 108 deletions(-) diff --git a/proto/go/gitalypb/hook.pb.go b/proto/go/gitalypb/hook.pb.go index d0ce04f1736..160519b49de 100644 --- a/proto/go/gitalypb/hook.pb.go +++ b/proto/go/gitalypb/hook.pb.go @@ -390,33 +390,34 @@ func init() { func init() { proto.RegisterFile("hook.proto", fileDescriptor_3eef30da1c11ee1b) } var fileDescriptor_3eef30da1c11ee1b = []byte{ - // 415 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x53, 0xcd, 0x6e, 0xd3, 0x40, - 0x10, 0x96, 0x13, 0x12, 0xe2, 0x69, 0xc4, 0xcf, 0x8a, 0x06, 0x63, 0x04, 0x44, 0x3e, 0xf9, 0x82, - 0x0d, 0xe1, 0x0d, 0x38, 0xc1, 0x05, 0x55, 0x8b, 0xe0, 0x50, 0x09, 0x2a, 0xdb, 0x3b, 0xa4, 0x96, - 0x8d, 0xc7, 0xec, 0xae, 0x53, 0xf9, 0x3d, 0x90, 0xe0, 0x19, 0x78, 0x44, 0x4e, 0xc8, 0xbb, 0x4e, - 0x1a, 0xea, 0xf4, 0xd6, 0xde, 0xf6, 0x9b, 0x6f, 0x67, 0xe6, 0xfb, 0x66, 0x76, 0x01, 0xce, 0x89, - 0x8a, 0xa8, 0x96, 0xa4, 0x89, 0x4d, 0xd7, 0xb9, 0x4e, 0xca, 0xd6, 0x9f, 0xab, 0xf3, 0x44, 0xa2, - 0xb0, 0xd1, 0xe0, 0xa7, 0x03, 0xc7, 0x27, 0x12, 0x39, 0x66, 0x98, 0x6f, 0xf0, 0x1d, 0x51, 0xc1, - 0xf1, 0x47, 0x83, 0x4a, 0xb3, 0x15, 0x80, 0xc4, 0x9a, 0x54, 0xae, 0x49, 0xb6, 0x9e, 0xb3, 0x74, - 0xc2, 0xa3, 0x15, 0x8b, 0x6c, 0x91, 0x88, 0xef, 0x18, 0xbe, 0x77, 0x8b, 0x1d, 0xc3, 0xb4, 0xc0, - 0xf6, 0x2c, 0x17, 0xde, 0x68, 0xe9, 0x84, 0x2e, 0x9f, 0x14, 0xd8, 0xbe, 0x17, 0xcc, 0x87, 0x99, - 0xe9, 0x96, 0x51, 0xe9, 0x8d, 0x0d, 0xb1, 0xc3, 0xec, 0x11, 0x4c, 0x94, 0x16, 0x79, 0xe5, 0xdd, - 0x59, 0x3a, 0xe1, 0x9c, 0x5b, 0x10, 0xa4, 0xb0, 0xb8, 0xaa, 0x4a, 0xd5, 0x54, 0x29, 0x64, 0x0b, - 0x98, 0x2a, 0x2d, 0xa8, 0xd1, 0x46, 0xd2, 0x9c, 0xf7, 0xa8, 0x8f, 0xa3, 0x94, 0xa6, 0xb5, 0x8d, - 0xa3, 0x94, 0xcc, 0x83, 0xbb, 0xaa, 0xc9, 0x32, 0x54, 0xca, 0xb4, 0x9e, 0xf1, 0x2d, 0x0c, 0x5a, - 0x58, 0x9c, 0x90, 0xd2, 0xb7, 0x6b, 0x7d, 0x67, 0x6f, 0xbc, 0x6f, 0x2f, 0x83, 0xc7, 0x83, 0xd6, - 0x37, 0xee, 0xef, 0x8f, 0x03, 0x0f, 0x3f, 0xd5, 0x22, 0xd1, 0xb7, 0xe5, 0xed, 0x01, 0x8c, 0x25, - 0x7e, 0xeb, 0x9d, 0x75, 0x47, 0xf6, 0x14, 0x5c, 0x2a, 0xc5, 0xd9, 0x26, 0x29, 0x1b, 0x34, 0x0b, - 0x75, 0xf9, 0x8c, 0x4a, 0xf1, 0xb9, 0xc3, 0x1d, 0x59, 0xe1, 0x45, 0x4f, 0x4e, 0x2c, 0x59, 0xe1, - 0x85, 0x21, 0x83, 0xaf, 0xc0, 0xf6, 0xb5, 0xde, 0xf4, 0x30, 0x56, 0xbf, 0x47, 0x70, 0xd4, 0x95, - 0xfe, 0x88, 0x72, 0x93, 0x67, 0xc8, 0x4e, 0xe1, 0xde, 0xff, 0x0f, 0x8c, 0x3d, 0xdb, 0x0e, 0xe1, - 0xe0, 0x77, 0xf0, 0x9f, 0x5f, 0x47, 0x5b, 0xa9, 0x81, 0xfb, 0xf7, 0x57, 0x38, 0x99, 0x8d, 0x7c, - 0xe7, 0x35, 0xfb, 0x02, 0xf7, 0xaf, 0x6c, 0x97, 0x5d, 0x66, 0x1f, 0x7c, 0x71, 0xfe, 0x8b, 0x6b, - 0xf9, 0x61, 0xf9, 0x0f, 0x00, 0x97, 0xa3, 0x62, 0x4f, 0xb6, 0x99, 0x83, 0x55, 0xfb, 0xfe, 0x21, - 0x6a, 0x50, 0xef, 0xed, 0xab, 0xd3, 0xee, 0x5e, 0x99, 0xa4, 0x51, 0x46, 0xdf, 0x63, 0x7b, 0x7c, - 0x49, 0x72, 0x1d, 0xdb, 0xec, 0xd8, 0x7c, 0xd5, 0x78, 0x4d, 0x3d, 0xae, 0xd3, 0x74, 0x6a, 0x42, - 0x6f, 0xfe, 0x05, 0x00, 0x00, 0xff, 0xff, 0x74, 0x46, 0x6b, 0x70, 0x5f, 0x04, 0x00, 0x00, + // 419 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x53, 0x4d, 0x8f, 0xd3, 0x30, + 0x10, 0x95, 0x5b, 0x5a, 0xda, 0xd9, 0x8a, 0x0f, 0x8b, 0x2d, 0x21, 0x08, 0xa8, 0x72, 0xca, 0x85, + 0xb6, 0x94, 0x7f, 0xc0, 0x09, 0x6e, 0x95, 0x11, 0x1c, 0x38, 0xec, 0x2a, 0x89, 0x87, 0x6e, 0x94, + 0x90, 0x09, 0xb6, 0xd3, 0x55, 0xfe, 0x07, 0x12, 0x77, 0x8e, 0xfc, 0x44, 0x4e, 0x28, 0x76, 0xda, + 0xed, 0x6e, 0xb3, 0xb7, 0xdd, 0x9b, 0xdf, 0x3c, 0x7b, 0xde, 0xbc, 0x19, 0x0f, 0xc0, 0x05, 0x51, + 0x36, 0x2f, 0x15, 0x19, 0xe2, 0xc3, 0x4d, 0x6a, 0xa2, 0xbc, 0xf6, 0x27, 0xfa, 0x22, 0x52, 0x28, + 0x5d, 0x34, 0xf8, 0xc5, 0xe0, 0x74, 0xad, 0x50, 0x60, 0x82, 0xe9, 0x16, 0x3f, 0x12, 0x65, 0x02, + 0x7f, 0x56, 0xa8, 0x0d, 0x5f, 0x01, 0x28, 0x2c, 0x49, 0xa7, 0x86, 0x54, 0xed, 0xb1, 0x19, 0x0b, + 0x4f, 0x56, 0x7c, 0xee, 0x92, 0xcc, 0xc5, 0x9e, 0x11, 0x07, 0xb7, 0xf8, 0x29, 0x0c, 0x33, 0xac, + 0xcf, 0x53, 0xe9, 0xf5, 0x66, 0x2c, 0x1c, 0x8b, 0x41, 0x86, 0xf5, 0x27, 0xc9, 0x7d, 0x18, 0x59, + 0xb5, 0x84, 0x72, 0xaf, 0x6f, 0x89, 0x3d, 0xe6, 0xcf, 0x60, 0xa0, 0x8d, 0x4c, 0x0b, 0xef, 0xc1, + 0x8c, 0x85, 0x13, 0xe1, 0x40, 0x10, 0xc3, 0xf4, 0x66, 0x55, 0xba, 0xa4, 0x42, 0x23, 0x9f, 0xc2, + 0x50, 0x1b, 0x49, 0x95, 0xb1, 0x25, 0x4d, 0x44, 0x8b, 0xda, 0x38, 0x2a, 0x65, 0xa5, 0x5d, 0x1c, + 0x95, 0xe2, 0x1e, 0x3c, 0xd4, 0x55, 0x92, 0xa0, 0xd6, 0x56, 0x7a, 0x24, 0x76, 0x30, 0xa8, 0x61, + 0xba, 0x26, 0x6d, 0xee, 0xd7, 0xfa, 0xde, 0x5e, 0xff, 0xd0, 0x5e, 0x02, 0xcf, 0x8f, 0xa4, 0xef, + 0xdc, 0xdf, 0x5f, 0x06, 0x4f, 0xbf, 0x94, 0x32, 0x32, 0xf7, 0xe5, 0xed, 0x09, 0xf4, 0x15, 0x7e, + 0x6f, 0x9d, 0x35, 0x47, 0xfe, 0x12, 0xc6, 0x94, 0xcb, 0xf3, 0x6d, 0x94, 0x57, 0x68, 0x07, 0x3a, + 0x16, 0x23, 0xca, 0xe5, 0xd7, 0x06, 0x37, 0x64, 0x81, 0x97, 0x2d, 0x39, 0x70, 0x64, 0x81, 0x97, + 0x96, 0x0c, 0xce, 0x80, 0x1f, 0xd6, 0x7a, 0xd7, 0xcd, 0x58, 0xfd, 0xe9, 0xc1, 0x49, 0x93, 0xfa, + 0x33, 0xaa, 0x6d, 0x9a, 0x20, 0x3f, 0x83, 0x47, 0xd7, 0x3f, 0x18, 0x7f, 0xb5, 0x6b, 0x42, 0xe7, + 0x3a, 0xf8, 0xaf, 0x6f, 0xa3, 0x5d, 0xa9, 0xc1, 0xf8, 0xdf, 0xef, 0x70, 0x30, 0xea, 0xf9, 0xec, + 0x5d, 0xc8, 0x96, 0x8c, 0x47, 0xf0, 0xf8, 0xc6, 0x84, 0xf9, 0x55, 0x86, 0xce, 0x5f, 0xe7, 0xbf, + 0xb9, 0x95, 0xef, 0x96, 0x58, 0x03, 0x5c, 0xb5, 0x8c, 0xbf, 0xd8, 0xbd, 0x3e, 0x1a, 0xb9, 0xef, + 0x77, 0x51, 0x47, 0x39, 0x97, 0xec, 0xc3, 0xf2, 0x5b, 0x73, 0x33, 0x8f, 0xe2, 0x79, 0x42, 0x3f, + 0x16, 0xee, 0xf8, 0x96, 0xd4, 0x66, 0xe1, 0xde, 0x2f, 0xec, 0xd2, 0x2e, 0x36, 0xd4, 0xe2, 0x32, + 0x8e, 0x87, 0x36, 0xf4, 0xfe, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa5, 0xca, 0x64, 0xd9, 0x69, + 0x04, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -431,9 +432,9 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type HookServiceClient interface { - PreReceiveHook(ctx context.Context, in *PreReceiveHookRequest, opts ...grpc.CallOption) (*PreReceiveHookResponse, error) - PostReceiveHook(ctx context.Context, in *PostReceiveHookRequest, opts ...grpc.CallOption) (*PostReceiveHookResponse, error) - UpdateHook(ctx context.Context, in *UpdateHookRequest, opts ...grpc.CallOption) (*UpdateHookResponse, error) + PreReceiveHook(ctx context.Context, opts ...grpc.CallOption) (HookService_PreReceiveHookClient, error) + PostReceiveHook(ctx context.Context, opts ...grpc.CallOption) (HookService_PostReceiveHookClient, error) + UpdateHook(ctx context.Context, in *UpdateHookRequest, opts ...grpc.CallOption) (HookService_UpdateHookClient, error) } type hookServiceClient struct { @@ -444,129 +445,220 @@ func NewHookServiceClient(cc *grpc.ClientConn) HookServiceClient { return &hookServiceClient{cc} } -func (c *hookServiceClient) PreReceiveHook(ctx context.Context, in *PreReceiveHookRequest, opts ...grpc.CallOption) (*PreReceiveHookResponse, error) { - out := new(PreReceiveHookResponse) - err := c.cc.Invoke(ctx, "/gitaly.HookService/PreReceiveHook", in, out, opts...) +func (c *hookServiceClient) PreReceiveHook(ctx context.Context, opts ...grpc.CallOption) (HookService_PreReceiveHookClient, error) { + stream, err := c.cc.NewStream(ctx, &_HookService_serviceDesc.Streams[0], "/gitaly.HookService/PreReceiveHook", opts...) if err != nil { return nil, err } - return out, nil + x := &hookServicePreReceiveHookClient{stream} + return x, nil } -func (c *hookServiceClient) PostReceiveHook(ctx context.Context, in *PostReceiveHookRequest, opts ...grpc.CallOption) (*PostReceiveHookResponse, error) { - out := new(PostReceiveHookResponse) - err := c.cc.Invoke(ctx, "/gitaly.HookService/PostReceiveHook", in, out, opts...) +type HookService_PreReceiveHookClient interface { + Send(*PreReceiveHookRequest) error + Recv() (*PreReceiveHookResponse, error) + grpc.ClientStream +} + +type hookServicePreReceiveHookClient struct { + grpc.ClientStream +} + +func (x *hookServicePreReceiveHookClient) Send(m *PreReceiveHookRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *hookServicePreReceiveHookClient) Recv() (*PreReceiveHookResponse, error) { + m := new(PreReceiveHookResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *hookServiceClient) PostReceiveHook(ctx context.Context, opts ...grpc.CallOption) (HookService_PostReceiveHookClient, error) { + stream, err := c.cc.NewStream(ctx, &_HookService_serviceDesc.Streams[1], "/gitaly.HookService/PostReceiveHook", opts...) if err != nil { return nil, err } - return out, nil + x := &hookServicePostReceiveHookClient{stream} + return x, nil +} + +type HookService_PostReceiveHookClient interface { + Send(*PostReceiveHookRequest) error + Recv() (*PostReceiveHookResponse, error) + grpc.ClientStream +} + +type hookServicePostReceiveHookClient struct { + grpc.ClientStream +} + +func (x *hookServicePostReceiveHookClient) Send(m *PostReceiveHookRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *hookServicePostReceiveHookClient) Recv() (*PostReceiveHookResponse, error) { + m := new(PostReceiveHookResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil } -func (c *hookServiceClient) UpdateHook(ctx context.Context, in *UpdateHookRequest, opts ...grpc.CallOption) (*UpdateHookResponse, error) { - out := new(UpdateHookResponse) - err := c.cc.Invoke(ctx, "/gitaly.HookService/UpdateHook", in, out, opts...) +func (c *hookServiceClient) UpdateHook(ctx context.Context, in *UpdateHookRequest, opts ...grpc.CallOption) (HookService_UpdateHookClient, error) { + stream, err := c.cc.NewStream(ctx, &_HookService_serviceDesc.Streams[2], "/gitaly.HookService/UpdateHook", opts...) if err != nil { return nil, err } - return out, nil + x := &hookServiceUpdateHookClient{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 HookService_UpdateHookClient interface { + Recv() (*UpdateHookResponse, error) + grpc.ClientStream +} + +type hookServiceUpdateHookClient struct { + grpc.ClientStream +} + +func (x *hookServiceUpdateHookClient) Recv() (*UpdateHookResponse, error) { + m := new(UpdateHookResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil } // HookServiceServer is the server API for HookService service. type HookServiceServer interface { - PreReceiveHook(context.Context, *PreReceiveHookRequest) (*PreReceiveHookResponse, error) - PostReceiveHook(context.Context, *PostReceiveHookRequest) (*PostReceiveHookResponse, error) - UpdateHook(context.Context, *UpdateHookRequest) (*UpdateHookResponse, error) + PreReceiveHook(HookService_PreReceiveHookServer) error + PostReceiveHook(HookService_PostReceiveHookServer) error + UpdateHook(*UpdateHookRequest, HookService_UpdateHookServer) error } // UnimplementedHookServiceServer can be embedded to have forward compatible implementations. type UnimplementedHookServiceServer struct { } -func (*UnimplementedHookServiceServer) PreReceiveHook(ctx context.Context, req *PreReceiveHookRequest) (*PreReceiveHookResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method PreReceiveHook not implemented") +func (*UnimplementedHookServiceServer) PreReceiveHook(srv HookService_PreReceiveHookServer) error { + return status.Errorf(codes.Unimplemented, "method PreReceiveHook not implemented") } -func (*UnimplementedHookServiceServer) PostReceiveHook(ctx context.Context, req *PostReceiveHookRequest) (*PostReceiveHookResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method PostReceiveHook not implemented") +func (*UnimplementedHookServiceServer) PostReceiveHook(srv HookService_PostReceiveHookServer) error { + return status.Errorf(codes.Unimplemented, "method PostReceiveHook not implemented") } -func (*UnimplementedHookServiceServer) UpdateHook(ctx context.Context, req *UpdateHookRequest) (*UpdateHookResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method UpdateHook not implemented") +func (*UnimplementedHookServiceServer) UpdateHook(req *UpdateHookRequest, srv HookService_UpdateHookServer) error { + return status.Errorf(codes.Unimplemented, "method UpdateHook not implemented") } func RegisterHookServiceServer(s *grpc.Server, srv HookServiceServer) { s.RegisterService(&_HookService_serviceDesc, srv) } -func _HookService_PreReceiveHook_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(PreReceiveHookRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HookServiceServer).PreReceiveHook(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/gitaly.HookService/PreReceiveHook", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HookServiceServer).PreReceiveHook(ctx, req.(*PreReceiveHookRequest)) - } - return interceptor(ctx, in, info, handler) +func _HookService_PreReceiveHook_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(HookServiceServer).PreReceiveHook(&hookServicePreReceiveHookServer{stream}) +} + +type HookService_PreReceiveHookServer interface { + Send(*PreReceiveHookResponse) error + Recv() (*PreReceiveHookRequest, error) + grpc.ServerStream +} + +type hookServicePreReceiveHookServer struct { + grpc.ServerStream +} + +func (x *hookServicePreReceiveHookServer) Send(m *PreReceiveHookResponse) error { + return x.ServerStream.SendMsg(m) } -func _HookService_PostReceiveHook_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(PostReceiveHookRequest) - if err := dec(in); err != nil { +func (x *hookServicePreReceiveHookServer) Recv() (*PreReceiveHookRequest, error) { + m := new(PreReceiveHookRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } - if interceptor == nil { - return srv.(HookServiceServer).PostReceiveHook(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/gitaly.HookService/PostReceiveHook", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HookServiceServer).PostReceiveHook(ctx, req.(*PostReceiveHookRequest)) - } - return interceptor(ctx, in, info, handler) + return m, nil +} + +func _HookService_PostReceiveHook_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(HookServiceServer).PostReceiveHook(&hookServicePostReceiveHookServer{stream}) } -func _HookService_UpdateHook_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpdateHookRequest) - if err := dec(in); err != nil { +type HookService_PostReceiveHookServer interface { + Send(*PostReceiveHookResponse) error + Recv() (*PostReceiveHookRequest, error) + grpc.ServerStream +} + +type hookServicePostReceiveHookServer struct { + grpc.ServerStream +} + +func (x *hookServicePostReceiveHookServer) Send(m *PostReceiveHookResponse) error { + return x.ServerStream.SendMsg(m) +} + +func (x *hookServicePostReceiveHookServer) Recv() (*PostReceiveHookRequest, error) { + m := new(PostReceiveHookRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } - if interceptor == nil { - return srv.(HookServiceServer).UpdateHook(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/gitaly.HookService/UpdateHook", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HookServiceServer).UpdateHook(ctx, req.(*UpdateHookRequest)) + return m, nil +} + +func _HookService_UpdateHook_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(UpdateHookRequest) + if err := stream.RecvMsg(m); err != nil { + return err } - return interceptor(ctx, in, info, handler) + return srv.(HookServiceServer).UpdateHook(m, &hookServiceUpdateHookServer{stream}) +} + +type HookService_UpdateHookServer interface { + Send(*UpdateHookResponse) error + grpc.ServerStream +} + +type hookServiceUpdateHookServer struct { + grpc.ServerStream +} + +func (x *hookServiceUpdateHookServer) Send(m *UpdateHookResponse) error { + return x.ServerStream.SendMsg(m) } var _HookService_serviceDesc = grpc.ServiceDesc{ ServiceName: "gitaly.HookService", HandlerType: (*HookServiceServer)(nil), - Methods: []grpc.MethodDesc{ + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ { - MethodName: "PreReceiveHook", - Handler: _HookService_PreReceiveHook_Handler, + StreamName: "PreReceiveHook", + Handler: _HookService_PreReceiveHook_Handler, + ServerStreams: true, + ClientStreams: true, }, { - MethodName: "PostReceiveHook", - Handler: _HookService_PostReceiveHook_Handler, + StreamName: "PostReceiveHook", + Handler: _HookService_PostReceiveHook_Handler, + ServerStreams: true, + ClientStreams: true, }, { - MethodName: "UpdateHook", - Handler: _HookService_UpdateHook_Handler, + StreamName: "UpdateHook", + Handler: _HookService_UpdateHook_Handler, + ServerStreams: true, }, }, - Streams: []grpc.StreamDesc{}, Metadata: "hook.proto", } diff --git a/proto/hook.proto b/proto/hook.proto index 2410d3b286c..487692d1ed8 100644 --- a/proto/hook.proto +++ b/proto/hook.proto @@ -7,19 +7,19 @@ option go_package = "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"; import "shared.proto"; service HookService { - rpc PreReceiveHook(PreReceiveHookRequest) returns (PreReceiveHookResponse) { + rpc PreReceiveHook(stream PreReceiveHookRequest) returns (stream PreReceiveHookResponse) { option (op_type) = { op: ACCESSOR target_repository_field: "1" }; } - rpc PostReceiveHook(PostReceiveHookRequest) returns (PostReceiveHookResponse) { + rpc PostReceiveHook(stream PostReceiveHookRequest) returns (stream PostReceiveHookResponse) { option (op_type) = { op: ACCESSOR target_repository_field: "1" }; } - rpc UpdateHook(UpdateHookRequest) returns (UpdateHookResponse) { + rpc UpdateHook(UpdateHookRequest) returns (stream UpdateHookResponse) { option (op_type) = { op: ACCESSOR target_repository_field: "1" diff --git a/ruby/proto/gitaly/hook_services_pb.rb b/ruby/proto/gitaly/hook_services_pb.rb index a57be1c361b..42d79188e80 100644 --- a/ruby/proto/gitaly/hook_services_pb.rb +++ b/ruby/proto/gitaly/hook_services_pb.rb @@ -14,9 +14,9 @@ module Gitaly self.unmarshal_class_method = :decode self.service_name = 'gitaly.HookService' - rpc :PreReceiveHook, PreReceiveHookRequest, PreReceiveHookResponse - rpc :PostReceiveHook, PostReceiveHookRequest, PostReceiveHookResponse - rpc :UpdateHook, UpdateHookRequest, UpdateHookResponse + rpc :PreReceiveHook, stream(PreReceiveHookRequest), stream(PreReceiveHookResponse) + rpc :PostReceiveHook, stream(PostReceiveHookRequest), stream(PostReceiveHookResponse) + rpc :UpdateHook, UpdateHookRequest, stream(UpdateHookResponse) end Stub = Service.rpc_stub_class -- GitLab