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

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Cai <jcai@gitlab.com>2020-01-08 22:03:36 +0300
committerJohn Cai <jcai@gitlab.com>2020-04-08 00:25:09 +0300
commit5f0116c3ac974f0285bfbec76107032e4e58e062 (patch)
tree5a7a9a96845f514ea343a9b54f8b586d72425b91
parent337158ffca931a7b3e9834cdba3263cfab84f223 (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.yml5
-rw-r--r--cmd/gitaly-hooks/hooks.go238
-rw-r--r--cmd/gitaly-hooks/hooks_test.go156
-rw-r--r--internal/git/receivepack.go17
-rw-r--r--internal/gitlabshell/env.go4
-rw-r--r--internal/log/hook.go5
-rw-r--r--internal/service/hooks/post_receive_test.go20
-rw-r--r--internal/service/hooks/pre_receive.go20
-rw-r--r--internal/service/hooks/pre_receive_test.go14
-rw-r--r--internal/service/hooks/update_test.go16
-rw-r--r--internal/service/smarthttp/receive_pack.go9
-rw-r--r--internal/service/smarthttp/receive_pack_test.go68
-rw-r--r--internal/service/smarthttp/testhelper_test.go10
-rw-r--r--internal/service/ssh/receive_pack.go14
-rw-r--r--internal/stream/std_stream.go5
-rw-r--r--internal/testhelper/testserver.go11
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",