diff options
author | John Cai <jcai@gitlab.com> | 2020-01-08 22:03:36 +0300 |
---|---|---|
committer | John Cai <jcai@gitlab.com> | 2020-04-08 00:25:09 +0300 |
commit | 5f0116c3ac974f0285bfbec76107032e4e58e062 (patch) | |
tree | 5a7a9a96845f514ea343a9b54f8b586d72425b91 | |
parent | 337158ffca931a7b3e9834cdba3263cfab84f223 (diff) |
Call Hook RPCs from gitaly-hooks binary
Migrate gitaly-hooks from calling the ruby hooks directly to calling the
hook RPCs through gitaly via an internal socket.
-rw-r--r-- | changelogs/unreleased/jc-call-hook-rpc.yml | 5 | ||||
-rw-r--r-- | cmd/gitaly-hooks/hooks.go | 238 | ||||
-rw-r--r-- | cmd/gitaly-hooks/hooks_test.go | 156 | ||||
-rw-r--r-- | internal/git/receivepack.go | 17 | ||||
-rw-r--r-- | internal/gitlabshell/env.go | 4 | ||||
-rw-r--r-- | internal/log/hook.go | 5 | ||||
-rw-r--r-- | internal/service/hooks/post_receive_test.go | 20 | ||||
-rw-r--r-- | internal/service/hooks/pre_receive.go | 20 | ||||
-rw-r--r-- | internal/service/hooks/pre_receive_test.go | 14 | ||||
-rw-r--r-- | internal/service/hooks/update_test.go | 16 | ||||
-rw-r--r-- | internal/service/smarthttp/receive_pack.go | 9 | ||||
-rw-r--r-- | internal/service/smarthttp/receive_pack_test.go | 68 | ||||
-rw-r--r-- | internal/service/smarthttp/testhelper_test.go | 10 | ||||
-rw-r--r-- | internal/service/ssh/receive_pack.go | 14 | ||||
-rw-r--r-- | internal/stream/std_stream.go | 5 | ||||
-rw-r--r-- | internal/testhelper/testserver.go | 11 |
16 files changed, 506 insertions, 106 deletions
diff --git a/changelogs/unreleased/jc-call-hook-rpc.yml b/changelogs/unreleased/jc-call-hook-rpc.yml new file mode 100644 index 000000000..093e1d2c1 --- /dev/null +++ b/changelogs/unreleased/jc-call-hook-rpc.yml @@ -0,0 +1,5 @@ +--- +title: Call Hook RPCs from gitaly-hooks binary +merge_request: 1740 +author: +type: performance diff --git a/cmd/gitaly-hooks/hooks.go b/cmd/gitaly-hooks/hooks.go index 1bc42aa59..552b9a663 100644 --- a/cmd/gitaly-hooks/hooks.go +++ b/cmd/gitaly-hooks/hooks.go @@ -4,23 +4,34 @@ import ( "context" "errors" "fmt" + "io" "log" "os" "os/exec" "path/filepath" + "strconv" + "strings" "github.com/BurntSushi/toml" + "github.com/golang/protobuf/jsonpb" + gitalyauth "gitlab.com/gitlab-org/gitaly/auth" + "gitlab.com/gitlab-org/gitaly/client" "gitlab.com/gitlab-org/gitaly/internal/command" "gitlab.com/gitlab-org/gitaly/internal/config" "gitlab.com/gitlab-org/gitaly/internal/gitlabshell" gitalylog "gitlab.com/gitlab-org/gitaly/internal/log" + "gitlab.com/gitlab-org/gitaly/internal/metadata/featureflag" + "gitlab.com/gitlab-org/gitaly/internal/stream" + "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" + "gitlab.com/gitlab-org/gitaly/streamio" + "google.golang.org/grpc" ) func main() { var logger = gitalylog.NewHookLogger() if len(os.Args) < 2 { - logger.Fatal(errors.New("requires hook name")) + logger.Fatalf("requires hook name. args: %v", os.Args) } subCmd := os.Args[1] @@ -36,17 +47,32 @@ func main() { os.Exit(status) } - gitalyRubyDir := os.Getenv("GITALY_RUBY_DIR") - if gitalyRubyDir == "" { - logger.Fatal(errors.New("GITALY_RUBY_DIR not set")) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + if os.Getenv(featureflag.HooksRPCEnvVar) != "true" { + executeScript(ctx, subCmd, logger) + return } - rubyHookPath := filepath.Join(gitalyRubyDir, "gitlab-shell", "hooks", subCmd) + repository, err := repositoryFromEnv() + if err != nil { + logger.Fatalf("error when getting repository: %v", err) + } - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + gitalySocket, ok := os.LookupEnv("GITALY_SOCKET") + if !ok { + logger.Fatal(errors.New("GITALY_SOCKET not set")) + } - var hookCmd *exec.Cmd + conn, err := client.Dial("unix://"+gitalySocket, dialOpts(os.Getenv("GITALY_TOKEN"))) + if err != nil { + logger.Fatalf("error when dialing: %v", err) + } + + hookClient := gitalypb.NewHookServiceClient(conn) + + hookStatus := int32(1) switch subCmd { case "update": @@ -54,22 +80,162 @@ func main() { if len(args) != 3 { logger.Fatal(errors.New("update hook missing required arguments")) } + ref, oldValue, newValue := args[0], args[1], args[2] - hookCmd = exec.Command(rubyHookPath, args...) - case "pre-receive", "post-receive": - hookCmd = exec.Command(rubyHookPath) + req := &gitalypb.UpdateHookRequest{ + Repository: repository, + EnvironmentVariables: glValues(), + Ref: []byte(ref), + OldValue: oldValue, + NewValue: newValue, + } + + updateHookStream, err := hookClient.UpdateHook(ctx, req) + if err != nil { + logger.Fatalf("error when starting command for %v: %v", subCmd, err) + } + + if hookStatus, err = stream.Handler(func() (stream.StdoutStderrResponse, error) { + return updateHookStream.Recv() + }, noopSender, os.Stdout, os.Stderr); err != nil { + logger.Fatalf("error when receiving data for %v: %v", subCmd, err) + } + case "pre-receive": + preReceiveHookStream, err := hookClient.PreReceiveHook(ctx) + if err != nil { + logger.Fatalf("error when getting preReceiveHookStream client for %v: %v", subCmd, err) + } + + if err := preReceiveHookStream.Send(&gitalypb.PreReceiveHookRequest{ + Repository: repository, + EnvironmentVariables: glValues(), + }); err != nil { + logger.Fatalf("error when sending request for %v: %v", subCmd, err) + } + + f := sendFunc(streamio.NewWriter(func(p []byte) error { + return preReceiveHookStream.Send(&gitalypb.PreReceiveHookRequest{Stdin: p}) + }), preReceiveHookStream, os.Stdin) + + if hookStatus, err = stream.Handler(func() (stream.StdoutStderrResponse, error) { + return preReceiveHookStream.Recv() + }, f, os.Stdout, os.Stderr); err != nil { + logger.Fatalf("error when receiving data for %v: %v", subCmd, err) + } + case "post-receive": + postReceiveHookStream, err := hookClient.PostReceiveHook(ctx) + if err != nil { + logger.Fatalf("error when getting stream client for %v: %v", subCmd, err) + } + + if err := postReceiveHookStream.Send(&gitalypb.PostReceiveHookRequest{ + Repository: repository, + EnvironmentVariables: glValues(), + GitPushOptions: gitPushOptions(), + }); err != nil { + logger.Fatalf("error when sending request for %v: %v", subCmd, err) + } + + f := sendFunc(streamio.NewWriter(func(p []byte) error { + return postReceiveHookStream.Send(&gitalypb.PostReceiveHookRequest{Stdin: p}) + }), postReceiveHookStream, os.Stdin) + if hookStatus, err = stream.Handler(func() (stream.StdoutStderrResponse, error) { + return postReceiveHookStream.Recv() + }, f, 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(fmt.Errorf("subcommand name invalid: %v", subCmd)) } - cmd, err := command.New(ctx, hookCmd, os.Stdin, os.Stdout, os.Stderr, os.Environ()...) + os.Exit(int(hookStatus)) +} + +func noopSender(c chan error) {} + +func repositoryFromEnv() (*gitalypb.Repository, error) { + repoString, ok := os.LookupEnv("GITALY_REPO") + if !ok { + return nil, errors.New("GITALY_REPO not found") + } + + var repo gitalypb.Repository + if err := jsonpb.UnmarshalString(repoString, &repo); err != nil { + return nil, err + } + + pwd, err := os.Getwd() if err != nil { - logger.Fatalf("error when starting command for %v: %v", rubyHookPath, err) + return nil, err } - if err = cmd.Wait(); err != nil { - os.Exit(1) + gitObjDirAbs, ok := os.LookupEnv("GIT_OBJECT_DIRECTORY") + if ok { + gitObjDir, err := filepath.Rel(pwd, gitObjDirAbs) + if err != nil { + return nil, err + } + repo.GitObjectDirectory = gitObjDir + } + gitAltObjDirsAbs, ok := os.LookupEnv("GIT_ALTERNATE_OBJECT_DIRECTORIES") + if ok { + var gitAltObjDirs []string + for _, gitAltObjDirAbs := range strings.Split(gitAltObjDirsAbs, ":") { + gitAltObjDir, err := filepath.Rel(pwd, gitAltObjDirAbs) + if err != nil { + return nil, err + } + gitAltObjDirs = append(gitAltObjDirs, gitAltObjDir) + } + + repo.GitAlternateObjectDirectories = gitAltObjDirs + } + + return &repo, nil +} + +func glValues() []string { + var glEnvVars []string + for _, kv := range os.Environ() { + if strings.HasPrefix(kv, "GL_") { + glEnvVars = append(glEnvVars, kv) + } + } + + return glEnvVars +} + +func gitPushOptions() []string { + var gitPushOptions []string + + gitPushOptionCount, err := strconv.Atoi(os.Getenv("GIT_PUSH_OPTION_COUNT")) + if err != nil { + return gitPushOptions + } + + for i := 0; i < gitPushOptionCount; i++ { + gitPushOptions = append(gitPushOptions, os.Getenv(fmt.Sprintf("GIT_PUSH_OPTION_%d", i))) + } + + return gitPushOptions +} + +func dialOpts(token string) []grpc.DialOption { + dialOpts := client.DefaultDialOpts + + if token != "" { + dialOpts = append(dialOpts, grpc.WithPerRPCCredentials(gitalyauth.RPCCredentials(token))) + } + + return dialOpts +} + +func sendFunc(reqWriter io.Writer, stream grpc.ClientStream, stdin io.Reader) func(errC chan error) { + return func(errC chan error) { + _, errSend := io.Copy(reqWriter, stdin) + stream.CloseSend() + errC <- errSend } } @@ -101,3 +267,43 @@ func check(configPath string) (int, error) { return 0, nil } + +func executeScript(ctx context.Context, subCmd string, logger *gitalylog.HookLogger) { + gitalyRubyDir := os.Getenv("GITALY_RUBY_DIR") + if gitalyRubyDir == "" { + logger.Fatal(errors.New("GITALY_RUBY_DIR not set")) + } + + rubyHookPath := filepath.Join(gitalyRubyDir, "gitlab-shell", "hooks", subCmd) + + var hookCmd *exec.Cmd + + switch subCmd { + case "update": + args := os.Args[2:] + if len(args) != 3 { + logger.Fatal(errors.New("update hook missing required arguments")) + } + + hookCmd = exec.Command(rubyHookPath, args...) + case "pre-receive", "post-receive": + hookCmd = exec.Command(rubyHookPath) + default: + logger.Fatal(fmt.Errorf("subcommand name invalid: %v", subCmd)) + } + + 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 err := cmd.Wait(); err != nil { + logger.Errorf("error when executing ruby hook: %v", err) + exitError, ok := err.(*exec.ExitError) + if ok { + os.Exit(exitError.ExitCode()) + } + } + + os.Exit(0) +} diff --git a/cmd/gitaly-hooks/hooks_test.go b/cmd/gitaly-hooks/hooks_test.go index 30955507f..152da93fd 100644 --- a/cmd/gitaly-hooks/hooks_test.go +++ b/cmd/gitaly-hooks/hooks_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net" "os" "os/exec" "path/filepath" @@ -15,7 +16,12 @@ import ( "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/gitaly/internal/command" "gitlab.com/gitlab-org/gitaly/internal/config" + "gitlab.com/gitlab-org/gitaly/internal/metadata/featureflag" + hook "gitlab.com/gitlab-org/gitaly/internal/service/hooks" "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 TestMain(m *testing.M) { @@ -33,7 +39,7 @@ func testMain(m *testing.M) int { } func TestHooksPrePostReceive(t *testing.T) { - _, testRepoPath, cleanupFn := testhelper.NewTestRepo(t) + testRepo, testRepoPath, cleanupFn := testhelper.NewTestRepo(t) defer cleanupFn() secretToken := "secret token" @@ -80,15 +86,39 @@ func TestHooksPrePostReceive(t *testing.T) { gitObjectDirRegex := regexp.MustCompile(`(?m)^GIT_OBJECT_DIRECTORY=(.*)$`) gitAlternateObjectDirRegex := regexp.MustCompile(`(?m)^GIT_ALTERNATE_OBJECT_DIRECTORIES=(.*)$`) + srv, socket := runHookServiceServer(t) + defer srv.Stop() + + testCases := []struct { + hookName string + callRPC bool + }{ + { + hookName: "pre-receive", + callRPC: false, + }, + { + hookName: "post-receive", + callRPC: false, + }, + { + hookName: "pre-receive", + callRPC: true, + }, + { + hookName: "post-receive", + callRPC: true, + }, + } - for _, hook := range []string{"pre-receive", "post-receive"} { - t.Run(hook, func(t *testing.T) { - customHookOutputPath, cleanup := testhelper.WriteEnvToCustomHook(t, testRepoPath, hook) + for _, tc := range testCases { + t.Run(fmt.Sprintf("hookName: %s, calling rpc: %v", tc.hookName, tc.callRPC), func(t *testing.T) { + customHookOutputPath, cleanup := testhelper.WriteEnvToCustomHook(t, testRepoPath, tc.hookName) defer cleanup() var stderr, stdout bytes.Buffer stdin := bytes.NewBuffer([]byte(changes)) - hookPath, err := filepath.Abs(fmt.Sprintf("../../ruby/git-hooks/%s", hook)) + hookPath, err := filepath.Abs(fmt.Sprintf("../../ruby/git-hooks/%s", tc.hookName)) require.NoError(t, err) cmd := exec.Command(hookPath) cmd.Stderr = &stderr @@ -97,6 +127,8 @@ func TestHooksPrePostReceive(t *testing.T) { cmd.Env = testhelper.EnvForHooks( t, tempGitlabShellDir, + socket, + testRepo, testhelper.GlHookValues{ GLID: glID, GLUsername: glUsername, @@ -107,6 +139,10 @@ func TestHooksPrePostReceive(t *testing.T) { }, gitPushOptions..., ) + + if tc.callRPC { + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=true", featureflag.HooksRPCEnvVar)) + } cmd.Dir = testRepoPath require.NoError(t, cmd.Run()) @@ -117,17 +153,18 @@ func TestHooksPrePostReceive(t *testing.T) { require.Contains(t, output, "GL_USERNAME="+glUsername) require.Contains(t, output, "GL_ID="+glID) require.Contains(t, output, "GL_REPOSITORY="+glRepository) - if hook != "pre-receive" { - require.Contains(t, output, "GL_PROTOCOL="+glProtocol) - } - gitObjectDirMatches := gitObjectDirRegex.FindStringSubmatch(output) - require.Len(t, gitObjectDirMatches, 2) - require.Equal(t, gitObjectDir, gitObjectDirMatches[1]) + if tc.hookName == "pre-receive" { + gitObjectDirMatches := gitObjectDirRegex.FindStringSubmatch(output) + require.Len(t, gitObjectDirMatches, 2) + require.Equal(t, gitObjectDir, gitObjectDirMatches[1]) - gitAlternateObjectDirMatches := gitAlternateObjectDirRegex.FindStringSubmatch(output) - require.Len(t, gitAlternateObjectDirMatches, 2) - require.Equal(t, strings.Join(gitAlternateObjectDirs, ":"), gitAlternateObjectDirMatches[1]) + gitAlternateObjectDirMatches := gitAlternateObjectDirRegex.FindStringSubmatch(output) + require.Len(t, gitAlternateObjectDirMatches, 2) + require.Equal(t, strings.Join(gitAlternateObjectDirs, ":"), gitAlternateObjectDirMatches[1]) + } else { + require.Contains(t, output, "GL_PROTOCOL="+glProtocol) + } }) } } @@ -142,16 +179,11 @@ func TestHooksUpdate(t *testing.T) { defer cleanup() testhelper.WriteTemporaryGitlabShellConfigFile(t, tempGitlabShellDir, testhelper.GitlabShellConfig{GitlabURL: "http://www.example.com"}) - _, testRepoPath, cleanupFn := testhelper.NewTestRepo(t) - defer cleanupFn() os.Symlink(filepath.Join(config.Config.GitlabShell.Dir, "config.yml"), filepath.Join(tempGitlabShellDir, "config.yml")) testhelper.WriteShellSecretFile(t, tempGitlabShellDir, "the wrong token") - customHookOutputPath, cleanup := testhelper.WriteEnvToCustomHook(t, testRepoPath, "update") - defer cleanup() - gitlabShellDir := config.Config.GitlabShell.Dir defer func() { config.Config.GitlabShell.Dir = gitlabShellDir @@ -159,22 +191,44 @@ func TestHooksUpdate(t *testing.T) { config.Config.GitlabShell.Dir = tempGitlabShellDir + srv, socket := runHookServiceServer(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")) - tempFilePath := filepath.Join(testRepoPath, "tempfile") - refval, oldval, newval := "refval", "oldval", "newval" - var stdout, stderr bytes.Buffer + for _, callRPC := range []bool{true, false} { + t.Run(fmt.Sprintf("call rpc: %t", callRPC), func(t *testing.T) { + testHooksUpdate(t, tempGitlabShellDir, socket, testhelper.GlHookValues{ + GLID: glID, + GLUsername: glUsername, + GLRepo: glRepository, + GLProtocol: glProtocol, + }, callRPC) + }) + } +} +func testHooksUpdate(t *testing.T, gitlabShellDir, socket string, glValues testhelper.GlHookValues, callRPC bool) { + testRepo, testRepoPath, cleanupFn := testhelper.NewTestRepo(t) + defer cleanupFn() + + refval, oldval, newval := "refval", "oldval", "newval" updateHookPath, err := filepath.Abs("../../ruby/git-hooks/update") require.NoError(t, err) cmd := exec.Command(updateHookPath, refval, oldval, newval) - cmd.Env = testhelper.EnvForHooks(t, tempGitlabShellDir, testhelper.GlHookValues{ - GLID: glID, - GLUsername: glUsername, - GLRepo: glRepository, - GLProtocol: glProtocol, - }) + cmd.Env = testhelper.EnvForHooks(t, gitlabShellDir, socket, testRepo, glValues) + cmd.Dir = testRepoPath + tempFilePath := filepath.Join(testRepoPath, "tempfile") + + customHookOutputPath, cleanup := testhelper.WriteEnvToCustomHook(t, testRepoPath, "update") + defer cleanup() + + var stdout, stderr bytes.Buffer + + if callRPC { + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=true", featureflag.HooksRPCEnvVar)) + } cmd.Stdout = &stdout cmd.Stderr = &stderr cmd.Dir = testRepoPath @@ -187,17 +241,16 @@ func TestHooksUpdate(t *testing.T) { var inputs []string - f, err := os.Open(tempFilePath) + b, err := ioutil.ReadFile(tempFilePath) require.NoError(t, err) - require.NoError(t, json.NewDecoder(f).Decode(&inputs)) + require.NoError(t, json.Unmarshal(b, &inputs)) require.Equal(t, []string{refval, oldval, newval}, inputs) - require.NoError(t, f.Close()) output := string(testhelper.MustReadFile(t, customHookOutputPath)) - require.Contains(t, output, "GL_USERNAME="+glUsername) - require.Contains(t, output, "GL_ID="+glID) - require.Contains(t, output, "GL_REPOSITORY="+glRepository) - require.Contains(t, output, "GL_PROTOCOL="+glProtocol) + require.Contains(t, output, "GL_USERNAME="+glValues.GLUsername) + require.Contains(t, output, "GL_ID="+glValues.GLID) + require.Contains(t, output, "GL_REPOSITORY="+glValues.GLRepo) + require.Contains(t, output, "GL_PROTOCOL="+glValues.GLProtocol) } func TestHooksPostReceiveFailed(t *testing.T) { @@ -211,7 +264,7 @@ func TestHooksPostReceiveFailed(t *testing.T) { tempGitlabShellDir, cleanup := testhelper.CreateTemporaryGitlabShellDir(t) defer cleanup() - _, testRepoPath, cleanupFn := testhelper.NewTestRepo(t) + testRepo, testRepoPath, cleanupFn := testhelper.NewTestRepo(t) defer cleanupFn() // By setting the last parameter to false, the post-receive API call will @@ -244,12 +297,15 @@ func TestHooksPostReceiveFailed(t *testing.T) { customHookOutputPath, cleanup := testhelper.WriteEnvToCustomHook(t, testRepoPath, "post-receive") defer cleanup() + srv, socket := runHookServiceServer(t) + defer srv.Stop() + var stdout, stderr bytes.Buffer postReceiveHookPath, err := filepath.Abs("../../ruby/git-hooks/post-receive") require.NoError(t, err) cmd := exec.Command(postReceiveHookPath) - cmd.Env = testhelper.EnvForHooks(t, tempGitlabShellDir, testhelper.GlHookValues{ + cmd.Env = testhelper.EnvForHooks(t, tempGitlabShellDir, socket, testRepo, testhelper.GlHookValues{ GLID: glID, GLUsername: glUsername, GLRepo: glRepository, @@ -283,6 +339,9 @@ func TestHooksNotAllowed(t *testing.T) { tempGitlabShellDir, cleanup := testhelper.CreateTemporaryGitlabShellDir(t) defer cleanup() + testRepo, testRepoPath, cleanupFn := testhelper.NewTestRepo(t) + defer cleanupFn() + c := testhelper.GitlabTestServerOptions{ User: "", Password: "", @@ -295,8 +354,6 @@ func TestHooksNotAllowed(t *testing.T) { } ts := testhelper.NewGitlabTestServer(t, c) defer ts.Close() - _, testRepoPath, cleanupFn := testhelper.NewTestRepo(t) - defer cleanupFn() testhelper.WriteTemporaryGitlabShellConfigFile(t, tempGitlabShellDir, testhelper.GitlabShellConfig{GitlabURL: ts.URL}) testhelper.WriteShellSecretFile(t, tempGitlabShellDir, "the wrong token") @@ -310,6 +367,8 @@ func TestHooksNotAllowed(t *testing.T) { defer cleanup() config.Config.GitlabShell.Dir = tempGitlabShellDir + srv, socket := runHookServiceServer(t) + defer srv.Stop() var stderr, stdout bytes.Buffer @@ -319,7 +378,7 @@ func TestHooksNotAllowed(t *testing.T) { cmd.Stderr = &stderr cmd.Stdout = &stdout cmd.Stdin = strings.NewReader(changes) - cmd.Env = testhelper.EnvForHooks(t, tempGitlabShellDir, testhelper.GlHookValues{ + cmd.Env = testhelper.EnvForHooks(t, tempGitlabShellDir, socket, testRepo, testhelper.GlHookValues{ GLID: glID, GLUsername: glUsername, GLRepo: glRepository, @@ -428,3 +487,20 @@ func TestCheckBadCreds(t *testing.T) { require.Equal(t, "Check GitLab API access: ", stdout.String()) require.Equal(t, "FAILED. code: 401\n", stderr.String()) } + +func runHookServiceServer(t *testing.T) (*grpc.Server, string) { + server := testhelper.NewTestGrpcServer(t, nil, nil) + + serverSocketPath := testhelper.GetTemporaryGitalySocketFileName() + listener, err := net.Listen("unix", serverSocketPath) + if err != nil { + t.Fatal(err) + } + + gitalypb.RegisterHookServiceServer(server, hook.NewServer()) + reflection.Register(server) + + go server.Serve(listener) + + return server, serverSocketPath +} diff --git a/internal/git/receivepack.go b/internal/git/receivepack.go index 7c09d96da..b321a83b1 100644 --- a/internal/git/receivepack.go +++ b/internal/git/receivepack.go @@ -3,8 +3,11 @@ package git import ( "fmt" + "github.com/golang/protobuf/jsonpb" + "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 @@ -13,16 +16,26 @@ type ReceivePackRequest interface { GetGlId() string GetGlUsername() string GetGlRepository() string + GetRepository() *gitalypb.Repository } +var jsonpbMarshaller = &jsonpb.Marshaler{} + // HookEnv is information we pass down to the Git hooks during // git-receive-pack. -func HookEnv(req ReceivePackRequest) []string { +func HookEnv(req ReceivePackRequest) ([]string, error) { + repo, err := jsonpbMarshaller.MarshalToString(req.GetRepository()) + if err != nil { + return nil, err + } + return append([]string{ fmt.Sprintf("GL_ID=%s", req.GetGlId()), fmt.Sprintf("GL_USERNAME=%s", req.GetGlUsername()), fmt.Sprintf("GL_REPOSITORY=%s", req.GetGlRepository()), - }, gitlabshell.Env()...) + fmt.Sprintf("GITALY_SOCKET=" + config.GitalyInternalSocketPath()), + fmt.Sprintf("GITALY_REPO=%s", repo), + }, gitlabshell.Env()...), nil } // ReceivePackConfig contains config options we want to enforce when diff --git a/internal/gitlabshell/env.go b/internal/gitlabshell/env.go index 79bae0646..4db3c4fd0 100644 --- a/internal/gitlabshell/env.go +++ b/internal/gitlabshell/env.go @@ -1,6 +1,8 @@ package gitlabshell -import "gitlab.com/gitlab-org/gitaly/internal/config" +import ( + "gitlab.com/gitlab-org/gitaly/internal/config" +) // Env is a helper that returns a slice with environment variables used by gitlab shell func Env() []string { diff --git a/internal/log/hook.go b/internal/log/hook.go index 54789ab3d..a8219b376 100644 --- a/internal/log/hook.go +++ b/internal/log/hook.go @@ -40,3 +40,8 @@ func (h *HookLogger) Fatalf(format string, a ...interface{}) { fmt.Fprintf(os.Stderr, "error executing git hook") h.logger.Fatalf(format, a...) } + +// Fatalf logs a formatted error at the Fatal level +func (h *HookLogger) Errorf(format string, a ...interface{}) { + h.logger.Errorf(format, a...) +} diff --git a/internal/service/hooks/post_receive_test.go b/internal/service/hooks/post_receive_test.go index fe7b4145d..451c322f7 100644 --- a/internal/service/hooks/post_receive_test.go +++ b/internal/service/hooks/post_receive_test.go @@ -67,7 +67,7 @@ func TestPostReceive(t *testing.T) { stdin: bytes.NewBufferString("a\nb\nc\nd\ne\nf\ng"), req: gitalypb.PostReceiveHookRequest{ Repository: testRepo, - EnvironmentVariables: []string{"GL_ID=key_id", "GL_USERNAME=username", "GL_PROTOCOL=protocol"}, + EnvironmentVariables: []string{"GL_ID=key_id", "GL_USERNAME=username", "GL_PROTOCOL=protocol", "GL_REPOSITORY=repository"}, GitPushOptions: []string{"option0", "option1"}}, status: 0, stdout: "OK", @@ -78,7 +78,7 @@ func TestPostReceive(t *testing.T) { stdin: bytes.NewBuffer(nil), req: gitalypb.PostReceiveHookRequest{ Repository: testRepo, - EnvironmentVariables: []string{"GL_ID=key_id", "GL_USERNAME=username", "GL_PROTOCOL=protocol"}, + EnvironmentVariables: []string{"GL_ID=key_id", "GL_USERNAME=username", "GL_PROTOCOL=protocol", "GL_REPOSITORY=repository"}, GitPushOptions: []string{"option0"}, }, status: 1, @@ -90,7 +90,7 @@ func TestPostReceive(t *testing.T) { stdin: bytes.NewBufferString("a\nb\nc\nd\ne\nf\ng"), req: gitalypb.PostReceiveHookRequest{ Repository: testRepo, - EnvironmentVariables: []string{"GL_ID=", "GL_USERNAME=username", "GL_PROTOCOL=protocol"}, + EnvironmentVariables: []string{"GL_ID=", "GL_USERNAME=username", "GL_PROTOCOL=protocol", "GL_REPOSITORY=repository"}, GitPushOptions: []string{"option0"}, }, status: 1, @@ -102,7 +102,7 @@ func TestPostReceive(t *testing.T) { stdin: bytes.NewBufferString("a\nb\nc\nd\ne\nf\ng"), req: gitalypb.PostReceiveHookRequest{ Repository: testRepo, - EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=", "GL_PROTOCOL=protocol"}, + EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=", "GL_PROTOCOL=protocol", "GL_REPOSITORY=repository"}, GitPushOptions: []string{"option0"}, }, status: 1, @@ -114,7 +114,7 @@ func TestPostReceive(t *testing.T) { stdin: bytes.NewBufferString("a\nb\nc\nd\ne\nf\ng"), req: gitalypb.PostReceiveHookRequest{ Repository: testRepo, - EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL="}, + EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL=", "GL_REPOSITORY=repository"}, GitPushOptions: []string{"option0"}, }, status: 1, @@ -125,12 +125,8 @@ func TestPostReceive(t *testing.T) { desc: "missing gl_repository value", stdin: bytes.NewBufferString("a\nb\nc\nd\ne\nf\ng"), req: gitalypb.PostReceiveHookRequest{ - Repository: &gitalypb.Repository{ - StorageName: testRepo.GetStorageName(), - RelativePath: testRepo.GetRelativePath(), - GlRepository: "", - }, - EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL=protocol"}, + Repository: testRepo, + EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL=protocol", "GL_REPOSITORY="}, GitPushOptions: []string{"option0"}, }, status: 1, @@ -142,7 +138,7 @@ func TestPostReceive(t *testing.T) { stdin: bytes.NewBufferString("a\nb\nc\nd\ne\nf\ng"), req: gitalypb.PostReceiveHookRequest{ Repository: testRepo, - EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL=protocol"}, + EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL=protocol", "GL_REPOSITORY=repository"}, }, status: 1, stdout: "", diff --git a/internal/service/hooks/pre_receive.go b/internal/service/hooks/pre_receive.go index 6458c68b5..0564bb71c 100644 --- a/internal/service/hooks/pre_receive.go +++ b/internal/service/hooks/pre_receive.go @@ -2,11 +2,11 @@ package hook import ( "errors" - "fmt" "os/exec" "path/filepath" "gitlab.com/gitlab-org/gitaly/internal/config" + "gitlab.com/gitlab-org/gitaly/internal/git/alternates" "gitlab.com/gitlab-org/gitaly/internal/gitlabshell" "gitlab.com/gitlab-org/gitaly/internal/helper" "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" @@ -19,8 +19,15 @@ type hookRequest interface { } func hookRequestEnv(req hookRequest) []string { - return append(gitlabshell.Env(), - append(req.GetEnvironmentVariables(), fmt.Sprintf("GL_REPOSITORY=%s", req.GetRepository().GetGlRepository()))...) + return append(gitlabshell.Env(), req.GetEnvironmentVariables()...) +} + +func preReceiveEnv(req hookRequest) ([]string, error) { + _, env, err := alternates.PathAndEnv(req.GetRepository()) + if err != nil { + return nil, err + } + return append(hookRequestEnv(req), env...), nil } func gitlabShellHook(hookName string) string { @@ -52,12 +59,17 @@ func (s *server) PreReceiveHook(stream gitalypb.HookService_PreReceiveHookServer c := exec.Command(gitlabShellHook("pre-receive")) c.Dir = repoPath + env, err := preReceiveEnv(firstRequest) + if err != nil { + return helper.ErrInternal(err) + } + status, err := streamCommandResponse( stream.Context(), stdin, stdout, stderr, c, - hookRequestEnv(firstRequest), + env, ) if err != nil { diff --git a/internal/service/hooks/pre_receive_test.go b/internal/service/hooks/pre_receive_test.go index 2a2d40a57..e1d89dccf 100644 --- a/internal/service/hooks/pre_receive_test.go +++ b/internal/service/hooks/pre_receive_test.go @@ -73,6 +73,7 @@ func TestPreReceive(t *testing.T) { "GL_ID=key-123", "GL_PROTOCOL=protocol", "GL_USERNAME=username", + "GL_REPOSITORY=repository", }, }, status: 0, @@ -88,6 +89,7 @@ func TestPreReceive(t *testing.T) { "GL_ID=key-123", "GL_PROTOCOL=protocol", "GL_USERNAME=username", + "GL_REPOSITORY=repository", }, }, status: 1, @@ -103,6 +105,7 @@ func TestPreReceive(t *testing.T) { "GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL=", + "GL_REPOSITORY=repository", }, }, status: 1, @@ -118,6 +121,7 @@ func TestPreReceive(t *testing.T) { "GL_ID=", "GL_PROTOCOL=protocol", "GL_USERNAME=username", + "GL_REPOSITORY=repository", }, }, status: 1, @@ -133,6 +137,7 @@ func TestPreReceive(t *testing.T) { "GL_ID=key-123", "GL_PROTOCOL=protocol", "GL_USERNAME=", + "GL_REPOSITORY=repository", }, }, status: 1, @@ -143,15 +148,12 @@ func TestPreReceive(t *testing.T) { desc: "missing gl_repository", stdin: bytes.NewBufferString("a\nb\nc\nd\ne\nf\ng"), req: gitalypb.PreReceiveHookRequest{ - Repository: &gitalypb.Repository{ - StorageName: testRepo.GetStorageName(), - RelativePath: testRepo.GetRelativePath(), - GlRepository: "", - }, + Repository: testRepo, EnvironmentVariables: []string{ "GL_ID=key-123", - "GL_PROTOCOL=", + "GL_PROTOCOL=protocol", "GL_USERNAME=username", + "GL_REPOSITORY=", }, }, status: 1, diff --git a/internal/service/hooks/update_test.go b/internal/service/hooks/update_test.go index 81e0f0639..1155371d3 100644 --- a/internal/service/hooks/update_test.go +++ b/internal/service/hooks/update_test.go @@ -68,7 +68,7 @@ func TestUpdate(t *testing.T) { Ref: []byte("master"), OldValue: "a", NewValue: "b", - EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL=protocol"}, + EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL=protocol", "GL_REPOSITORY=repository"}, }, status: 0, stdout: "OK", @@ -81,7 +81,7 @@ func TestUpdate(t *testing.T) { Ref: nil, OldValue: "a", NewValue: "b", - EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL=protocol"}, + EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL=protocol", "GL_REPOSITORY=repository"}, }, status: 1, stdout: "", @@ -94,7 +94,7 @@ func TestUpdate(t *testing.T) { Ref: []byte("master"), OldValue: "", NewValue: "b", - EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL=protocol"}, + EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL=protocol", "GL_REPOSITORY=repository"}, }, status: 1, stdout: "", @@ -107,7 +107,7 @@ func TestUpdate(t *testing.T) { Ref: []byte("master"), OldValue: "a", NewValue: "", - EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL=protocol"}, + EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL=protocol", "GL_REPOSITORY=repository"}, }, status: 1, stdout: "", @@ -120,7 +120,7 @@ func TestUpdate(t *testing.T) { Ref: []byte("master"), OldValue: "a", NewValue: "b", - EnvironmentVariables: []string{"GL_ID=", "GL_USERNAME=username", "GL_PROTOCOL=protocol"}, + EnvironmentVariables: []string{"GL_ID=", "GL_USERNAME=username", "GL_PROTOCOL=protocol", "GL_REPOSITORY=repository"}, }, status: 1, stdout: "", @@ -133,7 +133,7 @@ func TestUpdate(t *testing.T) { Ref: []byte("master"), OldValue: "a", NewValue: "b", - EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=", "GL_PROTOCOL=protocol"}, + EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=", "GL_PROTOCOL=protocol", "GL_REPOSITORY=repository"}, }, status: 1, stdout: "", @@ -146,7 +146,7 @@ func TestUpdate(t *testing.T) { Ref: []byte("master"), OldValue: "a", NewValue: "b", - EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL="}, + EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL=", "GL_REPOSITORY=repository"}, }, status: 1, stdout: "", @@ -163,7 +163,7 @@ func TestUpdate(t *testing.T) { Ref: []byte("master"), OldValue: "a", NewValue: "b", - EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL=protocol"}, + EnvironmentVariables: []string{"GL_ID=key-123", "GL_USERNAME=username", "GL_PROTOCOL=protocol", "GL_REPOSITORY="}, }, status: 1, stdout: "", diff --git a/internal/service/smarthttp/receive_pack.go b/internal/service/smarthttp/receive_pack.go index ed11ce77f..d66d14742 100644 --- a/internal/service/smarthttp/receive_pack.go +++ b/internal/service/smarthttp/receive_pack.go @@ -42,7 +42,11 @@ func (s *server) PostReceivePack(stream gitalypb.SmartHTTPService_PostReceivePac return stream.Send(&gitalypb.PostReceivePackResponse{Data: p}) }) - env := append(git.HookEnv(req), "GL_PROTOCOL=http") + hookEnv, err := git.HookEnv(req) + if err != nil { + return err + } + env := append(hookEnv, "GL_PROTOCOL=http") repoPath, err := helper.GetRepoPath(req.Repository) if err != nil { @@ -82,6 +86,9 @@ func validateReceivePackRequest(req *gitalypb.PostReceivePackRequest) error { if req.Data != nil { return status.Errorf(codes.InvalidArgument, "PostReceivePack: non-empty Data") } + if req.Repository == nil { + return helper.ErrInvalidArgumentf("PostReceivePack: empty Repository") + } return nil } diff --git a/internal/service/smarthttp/receive_pack_test.go b/internal/service/smarthttp/receive_pack_test.go index a774b2656..d79233b27 100644 --- a/internal/service/smarthttp/receive_pack_test.go +++ b/internal/service/smarthttp/receive_pack_test.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "io/ioutil" + "net" "os" "path" "path/filepath" @@ -18,10 +19,14 @@ import ( "gitlab.com/gitlab-org/gitaly/internal/git" "gitlab.com/gitlab-org/gitaly/internal/git/hooks" "gitlab.com/gitlab-org/gitaly/internal/helper/text" + "gitlab.com/gitlab-org/gitaly/internal/metadata/featureflag" + hook "gitlab.com/gitlab-org/gitaly/internal/service/hooks" "gitlab.com/gitlab-org/gitaly/internal/testhelper" "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" "gitlab.com/gitlab-org/gitaly/streamio" + "google.golang.org/grpc" "google.golang.org/grpc/codes" + "google.golang.org/grpc/reflection" ) func TestSuccessfulReceivePackRequest(t *testing.T) { @@ -327,13 +332,29 @@ func TestInvalidTimezone(t *testing.T) { testhelper.MustRunCommand(t, nil, "git", "-C", repoPath, "show", commit) } -func TestPostReceivePackToHooks(t *testing.T) { +func drainPostReceivePackResponse(stream gitalypb.SmartHTTPService_PostReceivePackClient) error { + var err error + for err == nil { + _, err = stream.Recv() + } + return err +} + +func TestPostReceivePackToHooks_WithRPC(t *testing.T) { + testPostReceivePackToHooks(t, true) +} + +func TestPostReceivePackToHooks_WithoutRPC(t *testing.T) { + testPostReceivePackToHooks(t, false) +} + +func testPostReceivePackToHooks(t *testing.T, callRPC bool) { secretToken := "secret token" glRepository := "some_repo" glID := "key-123" - socket, stop := runSmartHTTPServer(t) - defer stop() + server, socket := runSmartHTTPHookServiceServer(t) + defer server.Stop() client, conn := newSmartHTTPClient(t, "unix://"+socket) defer conn.Close() @@ -341,13 +362,16 @@ func TestPostReceivePackToHooks(t *testing.T) { tempGitlabShellDir, cleanup := testhelper.CreateTemporaryGitlabShellDir(t) defer cleanup() - gitlabShellDir := config.Config.GitlabShell.Dir - defer func() { + defer func(gitlabShellDir string) { config.Config.GitlabShell.Dir = gitlabShellDir - }() - + }(config.Config.GitlabShell.Dir) config.Config.GitlabShell.Dir = tempGitlabShellDir + defer func(override string) { + hooks.Override = override + }(hooks.Override) + hooks.Override = "" + repo, testRepoPath, cleanup := testhelper.NewTestRepo(t) defer cleanup() @@ -385,6 +409,10 @@ func TestPostReceivePackToHooks(t *testing.T) { ctx, cancel := testhelper.Context() defer cancel() + if callRPC { + ctx = featureflag.OutgoingCtxWithFeatureFlag(ctx, featureflag.HooksRPC) + } + stream, err := client.PostReceivePack(ctx) require.NoError(t, err) @@ -398,12 +426,28 @@ func TestPostReceivePackToHooks(t *testing.T) { expectedResponse := "0030\x01000eunpack ok\n0019ok refs/heads/master\n00000000" require.Equal(t, expectedResponse, string(response), "Expected response to be %q, got %q", expectedResponse, response) + require.Error(t, drainPostReceivePackResponse(stream), io.EOF) } -func drainPostReceivePackResponse(stream gitalypb.SmartHTTPService_PostReceivePackClient) error { - var err error - for err == nil { - _, err = stream.Recv() +func runSmartHTTPHookServiceServer(t *testing.T) (*grpc.Server, string) { + server := testhelper.NewTestGrpcServer(t, nil, nil) + + serverSocketPath := testhelper.GetTemporaryGitalySocketFileName() + listener, err := net.Listen("unix", serverSocketPath) + if err != nil { + t.Fatal(err) } - return err + internalListener, err := net.Listen("unix", config.GitalyInternalSocketPath()) + if err != nil { + t.Fatal(err) + } + + gitalypb.RegisterSmartHTTPServiceServer(server, NewServer()) + gitalypb.RegisterHookServiceServer(server, hook.NewServer()) + reflection.Register(server) + + go server.Serve(listener) + go server.Serve(internalListener) + + return server, "unix://" + serverSocketPath } diff --git a/internal/service/smarthttp/testhelper_test.go b/internal/service/smarthttp/testhelper_test.go index 1e867961e..212ff9a4c 100644 --- a/internal/service/smarthttp/testhelper_test.go +++ b/internal/service/smarthttp/testhelper_test.go @@ -1,10 +1,13 @@ package smarthttp import ( + "log" "os" + "path/filepath" "testing" "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/internal/config" "gitlab.com/gitlab-org/gitaly/internal/git/hooks" "gitlab.com/gitlab-org/gitaly/internal/testhelper" "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" @@ -24,6 +27,13 @@ func TestMain(m *testing.M) { func testMain(m *testing.M) int { hooks.Override = "/" + cwd, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + + config.Config.Ruby.Dir = filepath.Join(cwd, "../../../ruby") + testhelper.ConfigureGitalyHooksBinary() return m.Run() diff --git a/internal/service/ssh/receive_pack.go b/internal/service/ssh/receive_pack.go index af03f68b8..be5dbceae 100644 --- a/internal/service/ssh/receive_pack.go +++ b/internal/service/ssh/receive_pack.go @@ -1,6 +1,7 @@ package ssh import ( + "errors" "fmt" "strconv" @@ -53,7 +54,11 @@ func sshReceivePack(stream gitalypb.SSHService_SSHReceivePackServer, req *gitaly return stream.Send(&gitalypb.SSHReceivePackResponse{Stderr: p}) }) - env := append(git.HookEnv(req), "GL_PROTOCOL=ssh") + hookEnv, err := git.HookEnv(req) + if err != nil { + return err + } + env := append(hookEnv, "GL_PROTOCOL=ssh") repoPath, err := helper.GetRepoPath(req.Repository) if err != nil { @@ -93,10 +98,13 @@ func sshReceivePack(stream gitalypb.SSHService_SSHReceivePackServer, req *gitaly func validateFirstReceivePackRequest(req *gitalypb.SSHReceivePackRequest) error { if req.GlId == "" { - return fmt.Errorf("empty GlId") + return errors.New("empty GlId") } if req.Stdin != nil { - return fmt.Errorf("non-empty data in first request") + return errors.New("non-empty data in first request") + } + if req.Repository == nil { + return errors.New("repository is empty") } return nil diff --git a/internal/stream/std_stream.go b/internal/stream/std_stream.go index 908f149c8..a6ba0f0a2 100644 --- a/internal/stream/std_stream.go +++ b/internal/stream/std_stream.go @@ -7,12 +7,17 @@ import ( "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" ) +// StdoutStderrResponse is an interface for RPC responses that need to stream stderr and stdout type StdoutStderrResponse interface { GetExitStatus() *gitalypb.ExitStatus GetStderr() []byte GetStdout() []byte } +// Sender is a function that sends input data to the stream +type Sender func(chan error) + +// Handler takes care of sending and receiving to and from the stream func Handler(recv func() (StdoutStderrResponse, error), send func(chan error), stdout, stderr io.Writer) (int32, error) { var ( exitStatus int32 diff --git a/internal/testhelper/testserver.go b/internal/testhelper/testserver.go index b2fa98fb7..3cd70b9eb 100644 --- a/internal/testhelper/testserver.go +++ b/internal/testhelper/testserver.go @@ -17,6 +17,7 @@ import ( "time" "github.com/BurntSushi/toml" + "github.com/golang/protobuf/jsonpb" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus" grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags" @@ -30,6 +31,7 @@ import ( praefectconfig "gitlab.com/gitlab-org/gitaly/internal/praefect/config" "gitlab.com/gitlab-org/gitaly/internal/praefect/models" serverauth "gitlab.com/gitlab-org/gitaly/internal/server/auth" + "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" "google.golang.org/grpc" "google.golang.org/grpc/health" "google.golang.org/grpc/health/grpc_health_v1" @@ -463,11 +465,16 @@ type GlHookValues struct { GitAlternateObjectDirs []string } +var jsonpbMarshaller jsonpb.Marshaler + // EnvForHooks generates a set of environment variables for gitaly hooks -func EnvForHooks(t TB, gitlabShellDir string, glHookValues GlHookValues, gitPushOptions ...string) []string { +func EnvForHooks(t TB, gitlabShellDir, gitalySocket string, repo *gitalypb.Repository, glHookValues GlHookValues, gitPushOptions ...string) []string { rubyDir, err := filepath.Abs("../../ruby") require.NoError(t, err) + repoString, err := jsonpbMarshaller.MarshalToString(repo) + require.NoError(t, err) + env := append(append([]string{ fmt.Sprintf("GITALY_BIN_DIR=%s", config.Config.BinDir), fmt.Sprintf("GITALY_RUBY_DIR=%s", rubyDir), @@ -475,6 +482,8 @@ func EnvForHooks(t TB, gitlabShellDir string, glHookValues GlHookValues, gitPush fmt.Sprintf("GL_REPOSITORY=%s", glHookValues.GLRepo), fmt.Sprintf("GL_PROTOCOL=%s", glHookValues.GLProtocol), fmt.Sprintf("GL_USERNAME=%s", glHookValues.GLUsername), + fmt.Sprintf("GITALY_SOCKET=%s", gitalySocket), + fmt.Sprintf("GITALY_REPO=%v", repoString), fmt.Sprintf("GITALY_GITLAB_SHELL_DIR=%s", gitlabShellDir), fmt.Sprintf("GITALY_LOG_DIR=%s", gitlabShellDir), "GITALY_LOG_LEVEL=info", |