diff options
author | Zeger-Jan van de Weg <git@zjvandeweg.nl> | 2020-01-08 18:55:29 +0300 |
---|---|---|
committer | Zeger-Jan van de Weg <git@zjvandeweg.nl> | 2020-01-08 18:55:29 +0300 |
commit | 4fb323c51c38d1ef26a4e40bd49f3442794232c8 (patch) | |
tree | 856780b0dfc9f0d39d73dc668723a28e034a4a00 | |
parent | b1c0371664f6c65f7645180bfa5fbef55d58a346 (diff) | |
parent | 617a6eec741d955562c27511d232639c46924678 (diff) |
Merge branch 'jc-add-hook-rpcs' into 'master'
Add hook rpcs
See merge request gitlab-org/gitaly!1686
-rw-r--r-- | changelogs/unreleased/jc-add-hook-rpcs.yml | 5 | ||||
-rw-r--r-- | internal/service/hooks/post_receive.go | 70 | ||||
-rw-r--r-- | internal/service/hooks/post_receive_test.go | 134 | ||||
-rw-r--r-- | internal/service/hooks/pre_receive.go | 91 | ||||
-rw-r--r-- | internal/service/hooks/pre_receive_test.go | 145 | ||||
-rw-r--r-- | internal/service/hooks/server.go | 10 | ||||
-rw-r--r-- | internal/service/hooks/stream_command.go | 35 | ||||
-rwxr-xr-x | internal/service/hooks/testdata/gitlab-shell/hooks/post-receive | 8 | ||||
-rwxr-xr-x | internal/service/hooks/testdata/gitlab-shell/hooks/pre-receive | 9 | ||||
-rwxr-xr-x | internal/service/hooks/testdata/gitlab-shell/hooks/update | 9 | ||||
-rw-r--r-- | internal/service/hooks/testhelper_test.go | 47 | ||||
-rw-r--r-- | internal/service/hooks/update.go | 61 | ||||
-rw-r--r-- | internal/service/hooks/update_test.go | 162 | ||||
-rw-r--r-- | internal/service/register.go | 2 |
14 files changed, 788 insertions, 0 deletions
diff --git a/changelogs/unreleased/jc-add-hook-rpcs.yml b/changelogs/unreleased/jc-add-hook-rpcs.yml new file mode 100644 index 000000000..78a550468 --- /dev/null +++ b/changelogs/unreleased/jc-add-hook-rpcs.yml @@ -0,0 +1,5 @@ +--- +title: Add hook rpcs +merge_request: 1686 +author: +type: performance diff --git a/internal/service/hooks/post_receive.go b/internal/service/hooks/post_receive.go new file mode 100644 index 000000000..bfe6e628a --- /dev/null +++ b/internal/service/hooks/post_receive.go @@ -0,0 +1,70 @@ +package hook + +import ( + "errors" + "os/exec" + + "gitlab.com/gitlab-org/gitaly/streamio" + + "gitlab.com/gitlab-org/gitaly/internal/helper" + "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" +) + +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) + } + + hookEnv, err := hookRequestEnv(firstRequest) + if err != nil { + return helper.ErrInternal(err) + } + + stdin := streamio.NewReader(func() ([]byte, error) { + req, err := stream.Recv() + return req.GetStdin(), err + }) + stdout := streamio.NewWriter(func(p []byte) error { return stream.Send(&gitalypb.PostReceiveHookResponse{Stdout: p}) }) + stderr := streamio.NewWriter(func(p []byte) error { return stream.Send(&gitalypb.PostReceiveHookResponse{Stderr: p}) }) + + repoPath, err := helper.GetRepoPath(firstRequest.GetRepository()) + if err != nil { + return helper.ErrInternal(err) + } + + c := exec.Command(gitlabShellHook("post-receive")) + c.Dir = repoPath + + success, err := streamCommandResponse( + stream.Context(), + stdin, + stdout, stderr, + c, + hookEnv, + ) + + if err != nil { + return helper.ErrInternal(err) + } + + if err := stream.SendMsg(&gitalypb.PostReceiveHookResponse{ + Success: success, + }); 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 000000000..bf9adf0fa --- /dev/null +++ b/internal/service/hooks/post_receive_test.go @@ -0,0 +1,134 @@ +package hook + +import ( + "bytes" + "io" + "os" + "path/filepath" + "testing" + + "gitlab.com/gitlab-org/gitaly/internal/helper/text" + + "github.com/stretchr/testify/assert" + "gitlab.com/gitlab-org/gitaly/streamio" + + "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" +) + +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{}), "empty repository should result in an error") + _, err = stream.Recv() + + testhelper.RequireGrpcError(t, err, codes.InvalidArgument) +} + +func TestPostReceive(t *testing.T) { + rubyDir := config.Config.Ruby.Dir + defer func(rubyDir string) { + config.Config.Ruby.Dir = rubyDir + }(rubyDir) + + cwd, err := os.Getwd() + require.NoError(t, err) + config.Config.Ruby.Dir = filepath.Join(cwd, "testdata") + + server, serverSocketPath := runHooksServer(t) + defer server.Stop() + + testRepo, _, cleanupFn := testhelper.NewTestRepo(t) + defer cleanupFn() + + client, conn := newHooksClient(t, serverSocketPath) + defer conn.Close() + + testCases := []struct { + desc string + stdin io.Reader + req gitalypb.PostReceiveHookRequest + success bool + stdout string + stderr string + }{ + { + desc: "valid stdin", + stdin: bytes.NewBufferString("a\nb\nc\nd\ne\nf\ng"), + req: gitalypb.PostReceiveHookRequest{Repository: testRepo, KeyId: "key_id"}, + success: true, + stdout: "OK", + stderr: "", + }, + { + desc: "missing stdin", + stdin: bytes.NewBuffer(nil), + req: gitalypb.PostReceiveHookRequest{Repository: testRepo, KeyId: "key_id"}, + success: false, + stdout: "", + stderr: "FAIL", + }, + { + desc: "missing key_id", + stdin: bytes.NewBuffer(nil), + req: gitalypb.PostReceiveHookRequest{Repository: testRepo}, + success: false, + stdout: "", + stderr: "FAIL", + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + ctx, cancel := testhelper.Context() + defer cancel() + + stream, err := client.PostReceiveHook(ctx) + require.NoError(t, err) + require.NoError(t, stream.Send(&tc.req)) + + go func() { + writer := streamio.NewWriter(func(p []byte) error { + return stream.Send(&gitalypb.PostReceiveHookRequest{Stdin: p}) + }) + _, err := io.Copy(writer, tc.stdin) + require.NoError(t, err) + require.NoError(t, stream.CloseSend(), "close send") + }() + + var success bool + var stdout, stderr bytes.Buffer + for { + resp, err := stream.Recv() + if err == io.EOF { + break + } + + _, err = stdout.Write(resp.GetStdout()) + require.NoError(t, err) + stderr.Write(resp.GetStderr()) + require.NoError(t, err) + + success = resp.GetSuccess() + require.NoError(t, err) + } + + require.Equal(t, tc.success, success) + assert.Equal(t, tc.stderr, text.ChompBytes(stderr.Bytes()), "hook stderr") + assert.Equal(t, tc.stdout, text.ChompBytes(stdout.Bytes()), "hook stdout") + }) + } +} diff --git a/internal/service/hooks/pre_receive.go b/internal/service/hooks/pre_receive.go new file mode 100644 index 000000000..63ff2db90 --- /dev/null +++ b/internal/service/hooks/pre_receive.go @@ -0,0 +1,91 @@ +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" +) + +type hookRequest interface { + GetKeyId() string + GetRepository() *gitalypb.Repository +} + +func hookRequestEnv(req hookRequest) ([]string, error) { + return append(gitlabshell.Env(), + fmt.Sprintf("GL_ID=%s", req.GetKeyId()), + fmt.Sprintf("GL_REPOSITORY=%s", req.GetRepository().GetGlRepository())), nil +} + +func gitlabShellHook(hookName string) string { + return filepath.Join(config.Config.Ruby.Dir, "gitlab-shell", "hooks", hookName) +} + +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) + } + + hookEnv, err := hookRequestEnv(firstRequest) + if err != nil { + return helper.ErrInternal(err) + } + + hookEnv = append(hookEnv, fmt.Sprintf("GL_PROTOCOL=%s", firstRequest.GetProtocol())) + + stdin := streamio.NewReader(func() ([]byte, error) { + req, err := stream.Recv() + return req.GetStdin(), err + }) + stdout := streamio.NewWriter(func(p []byte) error { return stream.Send(&gitalypb.PreReceiveHookResponse{Stdout: p}) }) + stderr := streamio.NewWriter(func(p []byte) error { return stream.Send(&gitalypb.PreReceiveHookResponse{Stderr: p}) }) + + repoPath, err := helper.GetRepoPath(firstRequest.GetRepository()) + if err != nil { + return helper.ErrInternal(err) + } + + c := exec.Command(gitlabShellHook("pre-receive")) + c.Dir = repoPath + + success, err := streamCommandResponse( + stream.Context(), + stdin, + stdout, stderr, + c, + hookEnv, + ) + + if err != nil { + return helper.ErrInternal(err) + } + + if err := stream.SendMsg(&gitalypb.PreReceiveHookResponse{ + Success: success, + }); 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 000000000..52ce8ec7b --- /dev/null +++ b/internal/service/hooks/pre_receive_test.go @@ -0,0 +1,145 @@ +package hook + +import ( + "bytes" + "io" + "os" + "path/filepath" + "testing" + + "gitlab.com/gitlab-org/gitaly/internal/helper/text" + + "github.com/stretchr/testify/assert" + + "gitlab.com/gitlab-org/gitaly/streamio" + + "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" +) + +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) +} + +func TestPreReceive(t *testing.T) { + rubyDir := config.Config.Ruby.Dir + defer func(rubyDir string) { + config.Config.Ruby.Dir = rubyDir + }(rubyDir) + + cwd, err := os.Getwd() + require.NoError(t, err) + config.Config.Ruby.Dir = filepath.Join(cwd, "testdata") + + server, serverSocketPath := runHooksServer(t) + defer server.Stop() + + testRepo, _, cleanupFn := testhelper.NewTestRepo(t) + defer cleanupFn() + + client, conn := newHooksClient(t, serverSocketPath) + defer conn.Close() + + ctx, cancel := testhelper.Context() + defer cancel() + + testCases := []struct { + desc string + stdin io.Reader + req gitalypb.PreReceiveHookRequest + success bool + stdout, stderr string + }{ + { + desc: "valid stdin", + stdin: bytes.NewBufferString("a\nb\nc\nd\ne\nf\ng"), + req: gitalypb.PreReceiveHookRequest{Repository: testRepo, KeyId: "key_id", Protocol: "protocol"}, + success: true, + stdout: "OK", + stderr: "", + }, + { + desc: "missing stdin", + stdin: bytes.NewBuffer(nil), + req: gitalypb.PreReceiveHookRequest{Repository: testRepo, KeyId: "key_id", Protocol: "protocol"}, + success: false, + stdout: "", + stderr: "FAIL", + }, + { + desc: "missing protocol", + stdin: bytes.NewBufferString("a\nb\nc\nd\ne\nf\ng"), + req: gitalypb.PreReceiveHookRequest{Repository: testRepo, KeyId: "key_id"}, + success: false, + stdout: "", + stderr: "FAIL", + }, + { + desc: "missing key_id", + stdin: bytes.NewBufferString("a\nb\nc\nd\ne\nf\ng"), + req: gitalypb.PreReceiveHookRequest{Repository: testRepo, Protocol: "protocol"}, + success: false, + stdout: "", + stderr: "FAIL", + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + stream, err := client.PreReceiveHook(ctx) + require.NoError(t, err) + require.NoError(t, stream.Send(&tc.req)) + + go func() { + writer := streamio.NewWriter(func(p []byte) error { + return stream.Send(&gitalypb.PreReceiveHookRequest{Stdin: p}) + }) + _, err := io.Copy(writer, tc.stdin) + require.NoError(t, err) + require.NoError(t, stream.CloseSend(), "close send") + }() + + var success bool + var stdout, stderr bytes.Buffer + for { + resp, err := stream.Recv() + if err == io.EOF { + break + } + if err != nil { + t.Errorf("error when receiving stream: %v", err) + } + + _, err = stdout.Write(resp.GetStdout()) + require.NoError(t, err) + _, err = stderr.Write(resp.GetStderr()) + require.NoError(t, err) + + success = resp.GetSuccess() + require.NoError(t, err) + } + + require.Equal(t, tc.success, success) + assert.Equal(t, tc.stderr, text.ChompBytes(stderr.Bytes()), "hook stderr") + assert.Equal(t, tc.stdout, text.ChompBytes(stdout.Bytes()), "hook stdout") + }) + } +} diff --git a/internal/service/hooks/server.go b/internal/service/hooks/server.go new file mode 100644 index 000000000..1977af1df --- /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 000000000..30d8003bf --- /dev/null +++ b/internal/service/hooks/stream_command.go @@ -0,0 +1,35 @@ +package hook + +import ( + "context" + "io" + "os/exec" + + "gitlab.com/gitlab-org/gitaly/internal/command" + "gitlab.com/gitlab-org/gitaly/internal/helper" +) + +func streamCommandResponse( + ctx context.Context, + stdin io.Reader, + stdout, stderr io.Writer, + c *exec.Cmd, + env []string, +) (bool, error) { + cmd, err := command.New(ctx, c, stdin, stdout, stderr, env...) + if err != nil { + return false, helper.ErrInternal(err) + } + + err = cmd.Wait() + if err == nil { + return true, nil + } + + code, ok := command.ExitStatus(err) + if ok && code != 0 { + return false, nil + } + + return false, err +} diff --git a/internal/service/hooks/testdata/gitlab-shell/hooks/post-receive b/internal/service/hooks/testdata/gitlab-shell/hooks/post-receive new file mode 100755 index 000000000..6f0207819 --- /dev/null +++ b/internal/service/hooks/testdata/gitlab-shell/hooks/post-receive @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby + +# Tests inputs to post-receive + +abort("FAIL") if $stdin.read.empty? +abort("FAIL") if %w[GL_ID GL_REPOSITORY].any? { |k| ENV[k].empty? } + +puts "OK" diff --git a/internal/service/hooks/testdata/gitlab-shell/hooks/pre-receive b/internal/service/hooks/testdata/gitlab-shell/hooks/pre-receive new file mode 100755 index 000000000..fc967a761 --- /dev/null +++ b/internal/service/hooks/testdata/gitlab-shell/hooks/pre-receive @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby + +# Tests inputs to pre-receive + +abort("FAIL") if $stdin.read.empty? +abort("FAIL") if %w[GL_ID GL_REPOSITORY GL_PROTOCOL].any? { |k| ENV[k].empty? } + +puts "OK" + diff --git a/internal/service/hooks/testdata/gitlab-shell/hooks/update b/internal/service/hooks/testdata/gitlab-shell/hooks/update new file mode 100755 index 000000000..f7a121069 --- /dev/null +++ b/internal/service/hooks/testdata/gitlab-shell/hooks/update @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby + +# Tests inputs to update + +abort("FAIL") unless ARGV.size == 3 +abort("FAIL") if ARGV.any? { |arg| arg.empty? } +abort("FAIL") if ENV['GL_ID'].empty? + +puts "OK" diff --git a/internal/service/hooks/testhelper_test.go b/internal/service/hooks/testhelper_test.go new file mode 100644 index 000000000..72367ac75 --- /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 000000000..6cfad6093 --- /dev/null +++ b/internal/service/hooks/update.go @@ -0,0 +1,61 @@ +package hook + +import ( + "errors" + "os/exec" + + "gitlab.com/gitlab-org/gitaly/streamio" + + "gitlab.com/gitlab-org/gitaly/internal/helper" + "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" +) + +func (s *server) UpdateHook(in *gitalypb.UpdateHookRequest, stream gitalypb.HookService_UpdateHookServer) error { + if err := validateUpdateHookRequest(in); err != nil { + return helper.ErrInvalidArgument(err) + } + + hookEnv, err := hookRequestEnv(in) + if err != nil { + return helper.ErrInternal(err) + } + + stdout := streamio.NewWriter(func(p []byte) error { return stream.Send(&gitalypb.UpdateHookResponse{Stdout: p}) }) + stderr := streamio.NewWriter(func(p []byte) error { return stream.Send(&gitalypb.UpdateHookResponse{Stderr: p}) }) + + repoPath, err := helper.GetRepoPath(in.GetRepository()) + if err != nil { + return helper.ErrInternal(err) + } + + c := exec.Command(gitlabShellHook("update"), string(in.GetRef()), in.GetOldValue(), in.GetNewValue()) + c.Dir = repoPath + + success, err := streamCommandResponse( + stream.Context(), + nil, + stdout, stderr, + c, + hookEnv, + ) + + if err != nil { + return helper.ErrInternal(err) + } + + if err := stream.SendMsg(&gitalypb.PreReceiveHookResponse{ + Success: success, + }); 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 000000000..07b64a28c --- /dev/null +++ b/internal/service/hooks/update_test.go @@ -0,0 +1,162 @@ +package hook + +import ( + "bytes" + "io" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "gitlab.com/gitlab-org/gitaly/internal/helper/text" + + "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" +) + +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) +} + +func TestUpdate(t *testing.T) { + rubyDir := config.Config.Ruby.Dir + defer func() { + config.Config.Ruby.Dir = rubyDir + }() + + cwd, err := os.Getwd() + require.NoError(t, err) + config.Config.Ruby.Dir = filepath.Join(cwd, "testdata") + + server, serverSocketPath := runHooksServer(t) + defer server.Stop() + + testRepo, _, cleanupFn := testhelper.NewTestRepo(t) + defer cleanupFn() + + client, conn := newHooksClient(t, serverSocketPath) + defer conn.Close() + + ctx, cancel := testhelper.Context() + defer cancel() + + testCases := []struct { + desc string + req gitalypb.UpdateHookRequest + success bool + stdout, stderr string + }{ + { + desc: "valid inputs", + req: gitalypb.UpdateHookRequest{ + Repository: testRepo, + Ref: []byte("master"), + OldValue: "a", + NewValue: "b", + KeyId: "key", + }, + success: true, + stdout: "OK", + stderr: "", + }, + { + desc: "missing ref", + req: gitalypb.UpdateHookRequest{ + Repository: testRepo, + Ref: nil, + OldValue: "a", + NewValue: "b", + KeyId: "key", + }, + success: false, + stdout: "", + stderr: "FAIL", + }, + { + desc: "missing old value", + req: gitalypb.UpdateHookRequest{ + Repository: testRepo, + Ref: []byte("master"), + OldValue: "", + NewValue: "b", + KeyId: "key", + }, + success: false, + stdout: "", + stderr: "FAIL", + }, + { + desc: "missing new value", + req: gitalypb.UpdateHookRequest{ + Repository: testRepo, + Ref: []byte("master"), + OldValue: "a", + NewValue: "", + KeyId: "key", + }, + success: false, + stdout: "", + stderr: "FAIL", + }, + { + desc: "missing key_id value", + req: gitalypb.UpdateHookRequest{ + Repository: testRepo, + Ref: []byte("master"), + OldValue: "a", + NewValue: "b", + KeyId: "", + }, + success: false, + stdout: "", + stderr: "FAIL", + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + stream, err := client.UpdateHook(ctx, &tc.req) + require.NoError(t, err) + + var success bool + var stderr, stdout bytes.Buffer + for { + resp, err := stream.Recv() + if err == io.EOF { + break + } + + stderr.Write(resp.GetStderr()) + stdout.Write(resp.GetStdout()) + + if err != nil { + t.Errorf("error when receiving stream: %v", err) + } + + success = resp.GetSuccess() + require.NoError(t, err) + } + + require.Equal(t, tc.success, success) + assert.Equal(t, tc.stderr, text.ChompBytes(stderr.Bytes()), "hook stderr") + assert.Equal(t, tc.stdout, text.ChompBytes(stdout.Bytes()), "hook stdout") + }) + } +} diff --git a/internal/service/register.go b/internal/service/register.go index f12215e56..317e0a2a3 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()) } |