diff options
author | Ævar Arnfjörð Bjarmason <avar@gitlab.com> | 2020-12-15 01:44:46 +0300 |
---|---|---|
committer | Ævar Arnfjörð Bjarmason <avar@gitlab.com> | 2020-12-15 01:44:46 +0300 |
commit | 546f3976c32661c59246cc4b8eb73cfe4eba5b24 (patch) | |
tree | cf7fd1b38e72483e6fcf2a41af22b74a79dbf6cc | |
parent | 61c631b3e50b2a4db5c6c821d1d22906260f2f28 (diff) | |
parent | 4674e1f8ddecf47846327a670a7c6bcc4676fcc4 (diff) |
Merge branch 'pks-hooks-payload-gl-values' into 'master'
hooks: Convert GL_ values to be injected via the hooks payload
Closes #3201
See merge request gitlab-org/gitaly!2913
21 files changed, 473 insertions, 250 deletions
diff --git a/cmd/gitaly-hooks/hooks_test.go b/cmd/gitaly-hooks/hooks_test.go index c0efd9cfa..ab650b7ff 100644 --- a/cmd/gitaly-hooks/hooks_test.go +++ b/cmd/gitaly-hooks/hooks_test.go @@ -29,8 +29,8 @@ import ( ) type glHookValues struct { - GLID, GLUsername, GLRepo, GLProtocol, GitObjectDir string - GitAlternateObjectDirs []string + GLID, GLUsername, GLProtocol, GitObjectDir string + GitAlternateObjectDirs []string } type proxyValues struct { @@ -39,16 +39,16 @@ type proxyValues struct { // envForHooks generates a set of environment variables for gitaly hooks func envForHooks(t testing.TB, gitlabShellDir string, repo *gitalypb.Repository, glHookValues glHookValues, proxyValues proxyValues, gitPushOptions ...string) []string { - payload, err := git.NewHooksPayload(config.Config, repo, nil, nil).Env() + payload, err := git.NewHooksPayload(config.Config, repo, nil, nil, &git.ReceiveHooksPayload{ + UserID: glHookValues.GLID, + Username: glHookValues.GLUsername, + Protocol: glHookValues.GLProtocol, + }).Env() require.NoError(t, err) env := append(os.Environ(), []string{ payload, "GITALY_BIN_DIR=" + config.Config.BinDir, - fmt.Sprintf("GL_ID=%s", glHookValues.GLID), - fmt.Sprintf("GL_REPOSITORY=%s", glHookValues.GLRepo), - fmt.Sprintf("GL_PROTOCOL=%s", glHookValues.GLProtocol), - fmt.Sprintf("GL_USERNAME=%s", glHookValues.GLUsername), fmt.Sprintf("%s=%s", gitalylog.GitalyLogDirEnvKey, gitlabShellDir), }...) env = append(env, hooks.GitPushOptions(gitPushOptions)...) @@ -140,7 +140,6 @@ func testHooksPrePostReceive(t *testing.T) { glID := "key-1234" glUsername := "iamgitlab" glProtocol := "ssh" - glRepository := "some_repo" tempGitlabShellDir, cleanup := testhelper.CreateTemporaryGitlabShellDir(t) defer cleanup() @@ -159,7 +158,7 @@ func testHooksPrePostReceive(t *testing.T) { Password: gitlabPassword, SecretToken: secretToken, GLID: glID, - GLRepository: glRepository, + GLRepository: testRepo.GetGlRepository(), Changes: changes, PostReceiveCounterDecreased: true, Protocol: "ssh", @@ -228,7 +227,6 @@ func testHooksPrePostReceive(t *testing.T) { glHookValues{ GLID: glID, GLUsername: glUsername, - GLRepo: glRepository, GLProtocol: glProtocol, GitObjectDir: c.GitObjectDir, GitAlternateObjectDirs: c.GitAlternateObjectDirs, @@ -250,7 +248,7 @@ func testHooksPrePostReceive(t *testing.T) { output := string(testhelper.MustReadFile(t, customHookOutputPath)) requireContainsOnce(t, output, "GL_USERNAME="+glUsername) requireContainsOnce(t, output, "GL_ID="+glID) - requireContainsOnce(t, output, "GL_REPOSITORY="+glRepository) + requireContainsOnce(t, output, "GL_REPOSITORY="+testRepo.GetGlRepository()) requireContainsOnce(t, output, "HTTP_PROXY="+httpProxy) requireContainsOnce(t, output, "http_proxy="+httpProxy) requireContainsOnce(t, output, "HTTPS_PROXY="+httpsProxy) @@ -281,7 +279,6 @@ func TestHooksUpdate(t *testing.T) { glID := "key-1234" glUsername := "iamgitlab" glProtocol := "ssh" - glRepository := "some_repo" tempGitlabShellDir, cleanup := testhelper.CreateTemporaryGitlabShellDir(t) defer cleanup() @@ -306,7 +303,6 @@ func TestHooksUpdate(t *testing.T) { testHooksUpdate(t, tempGitlabShellDir, token, glHookValues{ GLID: glID, GLUsername: glUsername, - GLRepo: glRepository, GLProtocol: glProtocol, }) } @@ -366,7 +362,7 @@ open('%s', 'w') { |f| f.puts(JSON.dump(ARGV)) } output := string(testhelper.MustReadFile(t, customHookOutputPath)) 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_REPOSITORY="+testRepo.GetGlRepository()) require.Contains(t, output, "GL_PROTOCOL="+glValues.GLProtocol) } @@ -378,7 +374,6 @@ func TestHooksPostReceiveFailed(t *testing.T) { glID := "key-1234" glUsername := "iamgitlab" glProtocol := "ssh" - glRepository := "some_repo" changes := "oldhead newhead" tempGitlabShellDir, cleanup := testhelper.CreateTemporaryGitlabShellDir(t) @@ -397,7 +392,7 @@ func TestHooksPostReceiveFailed(t *testing.T) { SecretToken: secretToken, Changes: changes, GLID: glID, - GLRepository: glRepository, + GLRepository: testRepo.GetGlRepository(), PostReceiveCounterDecreased: false, Protocol: "ssh", } @@ -476,18 +471,15 @@ func TestHooksPostReceiveFailed(t *testing.T) { SocketPath: "/path/to/socket", Token: "secret", }, + &git.ReceiveHooksPayload{ + UserID: glID, + Username: glUsername, + Protocol: glProtocol, + }, ).Env() require.NoError(t, err) - env := envForHooks(t, tempGitlabShellDir, testRepo, - glHookValues{ - GLID: glID, - GLUsername: glUsername, - GLRepo: glRepository, - GLProtocol: glProtocol, - }, - proxyValues{}, - ) + env := envForHooks(t, tempGitlabShellDir, testRepo, glHookValues{}, proxyValues{}) env = append(env, hooksPayload) cmd := exec.Command(postReceiveHookPath) @@ -511,7 +503,6 @@ func TestHooksNotAllowed(t *testing.T) { glID := "key-1234" glUsername := "iamgitlab" glProtocol := "ssh" - glRepository := "some_repo" changes := "oldhead newhead" tempGitlabShellDir, cleanup := testhelper.CreateTemporaryGitlabShellDir(t) @@ -525,7 +516,7 @@ func TestHooksNotAllowed(t *testing.T) { Password: "", SecretToken: secretToken, GLID: glID, - GLRepository: glRepository, + GLRepository: testRepo.GetGlRepository(), Changes: changes, PostReceiveCounterDecreased: true, Protocol: "ssh", @@ -562,7 +553,6 @@ func TestHooksNotAllowed(t *testing.T) { glHookValues{ GLID: glID, GLUsername: glUsername, - GLRepo: glRepository, GLProtocol: glProtocol, }, proxyValues{}) diff --git a/internal/git/hooks_options.go b/internal/git/hooks_options.go index 2475dbbe0..642aa7f51 100644 --- a/internal/git/hooks_options.go +++ b/internal/git/hooks_options.go @@ -30,7 +30,7 @@ func WithRefTxHook(ctx context.Context, repo *gitalypb.Repository, cfg config.Cf return fmt.Errorf("missing repo: %w", ErrInvalidArg) } - if err := cc.configureHooks(ctx, repo, cfg); err != nil { + if err := cc.configureHooks(ctx, repo, cfg, nil); err != nil { return fmt.Errorf("ref hook env var: %w", err) } @@ -41,7 +41,12 @@ func WithRefTxHook(ctx context.Context, repo *gitalypb.Repository, cfg config.Cf // configureHooks updates the command configuration to include all environment // variables required by the reference transaction hook and any other needed // options to successfully execute hooks. -func (cc *cmdCfg) configureHooks(ctx context.Context, repo *gitalypb.Repository, cfg config.Cfg) error { +func (cc *cmdCfg) configureHooks( + ctx context.Context, + repo *gitalypb.Repository, + cfg config.Cfg, + receiveHooksPayload *ReceiveHooksPayload, +) error { if cc.hooksConfigured { return errors.New("hooks already configured") } @@ -51,7 +56,7 @@ func (cc *cmdCfg) configureHooks(ctx context.Context, repo *gitalypb.Repository, return err } - payload, err := NewHooksPayload(cfg, repo, transaction, praefect).Env() + payload, err := NewHooksPayload(cfg, repo, transaction, praefect, receiveHooksPayload).Env() if err != nil { return err } @@ -83,26 +88,14 @@ type ReceivePackRequest interface { // git-receive-pack(1). func WithReceivePackHooks(ctx context.Context, cfg config.Cfg, req ReceivePackRequest, protocol string) CmdOpt { return func(cc *cmdCfg) error { - if err := cc.configureHooks(ctx, req.GetRepository(), config.Config); err != nil { + if err := cc.configureHooks(ctx, req.GetRepository(), config.Config, &ReceiveHooksPayload{ + UserID: req.GetGlId(), + Username: req.GetGlUsername(), + Protocol: protocol, + }); err != nil { return err } - env, err := receivePackHookEnv(req, protocol) - if err != nil { - return fmt.Errorf("receive-pack hook envvars: %w", err) - } - - cc.env = append(cc.env, env...) return nil } } - -func receivePackHookEnv(req ReceivePackRequest, protocol string) ([]string, error) { - return []string{ - fmt.Sprintf("GL_ID=%s", req.GetGlId()), - fmt.Sprintf("GL_USERNAME=%s", req.GetGlUsername()), - fmt.Sprintf("GL_REPOSITORY=%s", req.GetGlRepository()), - fmt.Sprintf("GL_PROJECT_PATH=%s", req.GetRepository().GetGlProjectPath()), - fmt.Sprintf("GL_PROTOCOL=%s", protocol), - }, nil -} diff --git a/internal/git/hooks_payload.go b/internal/git/hooks_payload.go index 5bc1b193b..8852a34f3 100644 --- a/internal/git/hooks_payload.go +++ b/internal/git/hooks_payload.go @@ -40,8 +40,29 @@ type HooksPayload struct { // Gitaly's internal socket. InternalSocketToken string `json:"internal_socket_token"` - Transaction *metadata.Transaction `json:"transaction"` - Praefect *metadata.PraefectServer `json:"praefect"` + // Transaction is used to identify a reference transaction. This is an optional field -- if + // it's not set, no transactional voting will happen. + Transaction *metadata.Transaction `json:"transaction"` + // Praefect is used to identify the Praefect server which is hosting the transaction. This + // field must be set if and only if `Transaction` is. + Praefect *metadata.PraefectServer `json:"praefect"` + + // ReceiveHooksPayload contains information required when executing + // git-receive-pack. + ReceiveHooksPayload *ReceiveHooksPayload `json:"receive_hooks_payload"` +} + +// ReceiveHooksPayload contains all information which is required for hooks +// executed by git-receive-pack, namely the pre-receive, update or post-receive +// hook. +type ReceiveHooksPayload struct { + // Username contains the name of the user who has caused the hook to be executed. + Username string `json:"username"` + // UserID contains the ID of the user who has caused the hook to be executed. + UserID string `json:"userid"` + // Protocol contains the protocol via which the hook was executed. This + // can be one of "web", "ssh" or "smarthttp". + Protocol string `json:"protocol"` } // jsonHooksPayload wraps the HooksPayload such that we can manually encode the @@ -53,7 +74,13 @@ type jsonHooksPayload struct { // NewHooksPayload creates a new hooks payload which can then be encoded and // passed to Git hooks. -func NewHooksPayload(cfg config.Cfg, repo *gitalypb.Repository, tx *metadata.Transaction, praefect *metadata.PraefectServer) HooksPayload { +func NewHooksPayload( + cfg config.Cfg, + repo *gitalypb.Repository, + tx *metadata.Transaction, + praefect *metadata.PraefectServer, + receiveHooksPayload *ReceiveHooksPayload, +) HooksPayload { return HooksPayload{ Repo: repo, BinDir: cfg.BinDir, @@ -61,6 +88,7 @@ func NewHooksPayload(cfg config.Cfg, repo *gitalypb.Repository, tx *metadata.Tra InternalSocketToken: cfg.Auth.Token, Transaction: tx, Praefect: praefect, + ReceiveHooksPayload: receiveHooksPayload, } } @@ -131,6 +159,17 @@ func HooksPayloadFromEnv(envs []string) (HooksPayload, error) { } } + // If we didn't find a ReceiveHooksPayload, then we need to fallback to + // the GL_ environment values with the same reasoning as for + // `fallbackPayloadFromEnv()`. + if payload.ReceiveHooksPayload == nil { + receiveHooksPayload, err := fallbackReceiveHooksPayloadFromEnv(envs) + if err != nil { + return HooksPayload{}, err + } + payload.ReceiveHooksPayload = receiveHooksPayload + } + return payload, nil } @@ -173,6 +212,40 @@ func fallbackPayloadFromEnv(envs []string) (HooksPayload, error) { return payload, nil } +// fallbackReceiveHooksPayloadFromEnv is a compatibility layer for upgrades +// where it may happen that the new gitaly-hooks binary has already been +// installed while the old server is still running. +// +// We need to keep in mind that it's perfectly fine for hooks to not have +// the GL_ values in case we only run the reference-transaction hook. We cannot +// distinguish those cases, so the best we can do is check for the first value +// to exist: if it exists, we assume all the others must exist as well. +// Otherwise, we assume none exist. This should be fine as all three hooks +// expect those values to be set, while the reference-transaction hook doesn't +// care at all for them. +func fallbackReceiveHooksPayloadFromEnv(envs []string) (*ReceiveHooksPayload, error) { + protocol, ok := lookupEnv(envs, "GL_PROTOCOL") + if !ok { + return nil, nil + } + + userID, ok := lookupEnv(envs, "GL_ID") + if !ok { + return nil, errors.New("no user ID found in hooks environment") + } + + username, ok := lookupEnv(envs, "GL_USERNAME") + if !ok { + return nil, errors.New("no user ID found in hooks environment") + } + + return &ReceiveHooksPayload{ + Protocol: protocol, + UserID: userID, + Username: username, + }, nil +} + // Env encodes the given HooksPayload into an environment variable. func (p HooksPayload) Env() (string, error) { repo, err := jsonpbMarshaller.MarshalToString(p.Repo) diff --git a/internal/git/hooks_payload_test.go b/internal/git/hooks_payload_test.go index aa91e25ce..609b7d149 100644 --- a/internal/git/hooks_payload_test.go +++ b/internal/git/hooks_payload_test.go @@ -20,7 +20,7 @@ func TestHooksPayload(t *testing.T) { require.NoError(t, err) t.Run("envvar has proper name", func(t *testing.T) { - env, err := NewHooksPayload(config.Config, repo, nil, nil).Env() + env, err := NewHooksPayload(config.Config, repo, nil, nil, nil).Env() require.NoError(t, err) require.True(t, strings.HasPrefix(env, ErrHooksPayloadNotFound+"=")) }) @@ -43,7 +43,7 @@ func TestHooksPayload(t *testing.T) { require.NoError(t, err) t.Run("roundtrip succeeds", func(t *testing.T) { - env, err := NewHooksPayload(config.Config, repo, nil, nil).Env() + env, err := NewHooksPayload(config.Config, repo, nil, nil, nil).Env() require.NoError(t, err) payload, err := HooksPayloadFromEnv([]string{ @@ -62,7 +62,7 @@ func TestHooksPayload(t *testing.T) { }) t.Run("roundtrip with transaction succeeds", func(t *testing.T) { - env, err := NewHooksPayload(config.Config, repo, &tx, &praefect).Env() + env, err := NewHooksPayload(config.Config, repo, &tx, &praefect, nil).Env() require.NoError(t, err) payload, err := HooksPayloadFromEnv([]string{env}) @@ -195,7 +195,7 @@ func TestHooksPayload(t *testing.T) { }) t.Run("payload with missing Praefect", func(t *testing.T) { - env, err := NewHooksPayload(config.Config, repo, nil, nil).Env() + env, err := NewHooksPayload(config.Config, repo, nil, nil, nil).Env() require.NoError(t, err) _, err = HooksPayloadFromEnv([]string{env, txEnv}) @@ -203,7 +203,7 @@ func TestHooksPayload(t *testing.T) { }) t.Run("payload with fallback transaction", func(t *testing.T) { - env, err := NewHooksPayload(config.Config, repo, nil, nil).Env() + env, err := NewHooksPayload(config.Config, repo, nil, nil, nil).Env() require.NoError(t, err) payload, err := HooksPayloadFromEnv([]string{env, txEnv, praefectEnv}) @@ -218,4 +218,96 @@ func TestHooksPayload(t *testing.T) { Praefect: &praefect, }, payload) }) + + t.Run("fallback with GL_ values", func(t *testing.T) { + payload, err := HooksPayloadFromEnv([]string{ + "GITALY_BIN_DIR=/bin/dir", + "GITALY_SOCKET=/path/to/socket", + "GITALY_TOKEN=secret", + "GITALY_REPOSITORY=" + marshalledRepo, + "GL_ID=1234", + "GL_USERNAME=user", + "GL_PROTOCOL=ssh", + }) + require.NoError(t, err) + + require.Equal(t, HooksPayload{ + Repo: repo, + BinDir: "/bin/dir", + InternalSocket: "/path/to/socket", + InternalSocketToken: "secret", + ReceiveHooksPayload: &ReceiveHooksPayload{ + UserID: "1234", + Username: "user", + Protocol: "ssh", + }, + }, payload) + }) + + t.Run("payload with GL_ values", func(t *testing.T) { + env, err := NewHooksPayload(config.Config, repo, nil, nil, nil).Env() + require.NoError(t, err) + + payload, err := HooksPayloadFromEnv([]string{ + env, + "GL_ID=1234", + "GL_USERNAME=user", + "GL_PROTOCOL=ssh", + }) + require.NoError(t, err) + + require.Equal(t, HooksPayload{ + Repo: repo, + BinDir: config.Config.BinDir, + InternalSocket: config.Config.GitalyInternalSocketPath(), + InternalSocketToken: config.Config.Auth.Token, + ReceiveHooksPayload: &ReceiveHooksPayload{ + UserID: "1234", + Username: "user", + Protocol: "ssh", + }, + }, payload) + }) + + t.Run("payload with missing GL_ID", func(t *testing.T) { + env, err := NewHooksPayload(config.Config, repo, nil, nil, nil).Env() + require.NoError(t, err) + + _, err = HooksPayloadFromEnv([]string{ + env, + "GL_USERNAME=user", + "GL_PROTOCOL=ssh", + }) + require.Error(t, err) + require.Contains(t, "no user ID found in hooks environment", err.Error()) + }) + + t.Run("receive hooks payload", func(t *testing.T) { + env, err := NewHooksPayload(config.Config, repo, nil, nil, &ReceiveHooksPayload{ + UserID: "1234", + Username: "user", + Protocol: "ssh", + }).Env() + require.NoError(t, err) + + payload, err := HooksPayloadFromEnv([]string{ + env, + "GL_ID=wrong", + "GL_USERNAME=wrong", + "GL_PROTOCOL=wrong", + }) + require.NoError(t, err) + + require.Equal(t, HooksPayload{ + Repo: repo, + BinDir: config.Config.BinDir, + InternalSocket: config.Config.GitalyInternalSocketPath(), + InternalSocketToken: config.Config.Auth.Token, + ReceiveHooksPayload: &ReceiveHooksPayload{ + UserID: "1234", + Username: "user", + Protocol: "ssh", + }, + }, payload) + }) } diff --git a/internal/gitaly/hook/custom.go b/internal/gitaly/hook/custom.go index 1ed289ea3..2687f926c 100644 --- a/internal/gitaly/hook/custom.go +++ b/internal/gitaly/hook/custom.go @@ -12,6 +12,7 @@ import ( "strings" "gitlab.com/gitlab-org/gitaly/internal/command" + "gitlab.com/gitlab-org/gitaly/internal/git" "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" "golang.org/x/sys/unix" ) @@ -133,3 +134,13 @@ func validHook(fi os.FileInfo, filename string) bool { return true } + +func customHooksEnv(payload git.HooksPayload) []string { + return []string{ + "GL_REPOSITORY=" + payload.Repo.GetGlRepository(), + "GL_PROJECT_PATH=" + payload.Repo.GetGlProjectPath(), + "GL_ID=" + payload.ReceiveHooksPayload.UserID, + "GL_USERNAME=" + payload.ReceiveHooksPayload.Username, + "GL_PROTOCOL=" + payload.ReceiveHooksPayload.Protocol, + } +} diff --git a/internal/gitaly/hook/postreceive.go b/internal/gitaly/hook/postreceive.go index d6b1a1da9..da763cd32 100644 --- a/internal/gitaly/hook/postreceive.go +++ b/internal/gitaly/hook/postreceive.go @@ -135,15 +135,22 @@ func (m *GitLabHookManager) PostReceiveHook(ctx context.Context, repo *gitalypb. return helper.ErrInternalf("hook got no reference updates") } - glID, glRepo := getEnvVar("GL_ID", env), getEnvVar("GL_REPOSITORY", env) - if glID == "" { - return helper.ErrInternalf("GL_ID not set") + if payload.ReceiveHooksPayload == nil { + return helper.ErrInternalf("payload has no receive hooks info") } - if glRepo == "" { - return helper.ErrInternalf("GL_REPOSITORY not set") + if payload.ReceiveHooksPayload.UserID == "" { + return helper.ErrInternalf("user ID not set") + } + if repo.GetGlRepository() == "" { + return helper.ErrInternalf("repository not set") } - ok, messages, err := m.gitlabAPI.PostReceive(ctx, glRepo, glID, string(changes), pushOptions...) + ok, messages, err := m.gitlabAPI.PostReceive( + ctx, repo.GetGlRepository(), + payload.ReceiveHooksPayload.UserID, + string(changes), + pushOptions..., + ) if err != nil { return fmt.Errorf("GitLab: %v", err) } @@ -161,10 +168,13 @@ func (m *GitLabHookManager) PostReceiveHook(ctx context.Context, repo *gitalypb. return helper.ErrInternalf("creating custom hooks executor: %v", err) } + customHooksEnv := append(env, customHooksEnv(payload)...) + customHooksEnv = append(customHooksEnv, hooks.GitPushOptions(pushOptions)...) + if err = executor( ctx, nil, - append(env, hooks.GitPushOptions(pushOptions)...), + customHooksEnv, bytes.NewReader(changes), stdout, stderr, diff --git a/internal/gitaly/hook/postreceive_test.go b/internal/gitaly/hook/postreceive_test.go index 8cdd98735..113d06833 100644 --- a/internal/gitaly/hook/postreceive_test.go +++ b/internal/gitaly/hook/postreceive_test.go @@ -57,16 +57,6 @@ func TestPrintAlert(t *testing.T) { } } -func envWithout(envVars []string, value string) []string { - result := make([]string, 0, len(envVars)) - for _, envVar := range envVars { - if !strings.HasPrefix(envVar, fmt.Sprintf("%s=", value)) { - result = append(result, envVar) - } - } - return result -} - func TestPostReceive_customHook(t *testing.T) { repo, repoPath, cleanup := testhelper.NewTestRepo(t) defer cleanup() @@ -74,15 +64,16 @@ func TestPostReceive_customHook(t *testing.T) { hookManager := NewManager(config.NewLocator(config.Config), GitlabAPIStub, config.Config) standardEnv := []string{ - "GL_ID=1234", - fmt.Sprintf("GL_PROJECT_PATH=%s", repo.GetGlProjectPath()), - "GL_PROTOCOL=web", fmt.Sprintf("GL_REPO=%s", repo), - fmt.Sprintf("GL_REPOSITORY=%s", repo.GetGlRepository()), - "GL_USERNAME=user", } - payload, err := git.NewHooksPayload(config.Config, repo, nil, nil).Env() + receiveHooksPayload := &git.ReceiveHooksPayload{ + UserID: "1234", + Username: "user", + Protocol: "web", + } + + payload, err := git.NewHooksPayload(config.Config, repo, nil, nil, receiveHooksPayload).Env() require.NoError(t, err) primaryPayload, err := git.NewHooksPayload( @@ -95,6 +86,7 @@ func TestPostReceive_customHook(t *testing.T) { SocketPath: "/path/to/socket", Token: "secret", }, + receiveHooksPayload, ).Env() require.NoError(t, err) @@ -108,6 +100,7 @@ func TestPostReceive_customHook(t *testing.T) { SocketPath: "/path/to/socket", Token: "secret", }, + receiveHooksPayload, ).Env() require.NoError(t, err) @@ -122,11 +115,19 @@ func TestPostReceive_customHook(t *testing.T) { expectedStderr string }{ { - desc: "hook receives environment variables", - env: append(standardEnv, payload), - stdin: "changes\n", - hook: "#!/bin/sh\nenv | grep -e '^GL_' -e '^GITALY_' | sort\n", - expectedStdout: strings.Join(append([]string{payload}, standardEnv...), "\n") + "\n", + desc: "hook receives environment variables", + env: append(standardEnv, payload), + stdin: "changes\n", + hook: "#!/bin/sh\nenv | grep -e '^GL_' -e '^GITALY_' | sort\n", + expectedStdout: strings.Join([]string{ + payload, + "GL_ID=1234", + fmt.Sprintf("GL_PROJECT_PATH=%s", repo.GetGlProjectPath()), + "GL_PROTOCOL=web", + fmt.Sprintf("GL_REPO=%s", repo), + fmt.Sprintf("GL_REPOSITORY=%s", repo.GetGlRepository()), + "GL_USERNAME=user", + }, "\n") + "\n", }, { desc: "push options are passed through", @@ -193,18 +194,6 @@ func TestPostReceive_customHook(t *testing.T) { hook: "#!/bin/sh\necho foo\n", }, { - desc: "missing GL_ID causes error", - env: envWithout(append(standardEnv, payload), "GL_ID"), - stdin: "changes\n", - expectedErr: "GL_ID not set", - }, - { - desc: "missing GL_REPOSITORY causes error", - env: envWithout(append(standardEnv, payload), "GL_REPOSITORY"), - stdin: "changes\n", - expectedErr: "GL_REPOSITORY not set", - }, - { desc: "missing changes cause error", env: append(standardEnv, payload), expectedErr: "hook got no reference updates", @@ -259,17 +248,16 @@ func TestPostReceive_gitlab(t *testing.T) { testRepo, testRepoPath, cleanup := testhelper.NewTestRepo(t) defer cleanup() - payload, err := git.NewHooksPayload(config.Config, testRepo, nil, nil).Env() + payload, err := git.NewHooksPayload(config.Config, testRepo, nil, nil, &git.ReceiveHooksPayload{ + UserID: "1234", + Username: "user", + Protocol: "web", + }).Env() require.NoError(t, err) standardEnv := []string{ payload, - "GL_ID=1234", - fmt.Sprintf("GL_PROJECT_PATH=%s", testRepo.GetGlProjectPath()), - "GL_PROTOCOL=web", fmt.Sprintf("GL_REPO=%s", testRepo), - fmt.Sprintf("GL_REPOSITORY=%s", testRepo.GetGlRepository()), - "GL_USERNAME=user", } testCases := []struct { diff --git a/internal/gitaly/hook/prereceive.go b/internal/gitaly/hook/prereceive.go index a67fb59f8..9bd54f7c7 100644 --- a/internal/gitaly/hook/prereceive.go +++ b/internal/gitaly/hook/prereceive.go @@ -53,7 +53,7 @@ func (m *GitLabHookManager) PreReceiveHook(ctx context.Context, repo *gitalypb.R // Only the primary should execute hooks and increment reference counters. if isPrimary(payload) { - if err := m.preReceiveHook(ctx, repo, env, changes, stdout, stderr); err != nil { + if err := m.preReceiveHook(ctx, payload, repo, env, changes, stdout, stderr); err != nil { // If the pre-receive hook declines the push, then we need to stop any // secondaries voting on the transaction. m.stopTransaction(ctx, payload) @@ -64,7 +64,7 @@ func (m *GitLabHookManager) PreReceiveHook(ctx context.Context, repo *gitalypb.R return nil } -func (m *GitLabHookManager) preReceiveHook(ctx context.Context, repo *gitalypb.Repository, env []string, changes []byte, stdout, stderr io.Writer) error { +func (m *GitLabHookManager) preReceiveHook(ctx context.Context, payload git.HooksPayload, repo *gitalypb.Repository, env []string, changes []byte, stdout, stderr io.Writer) error { repoPath, err := m.locator.GetRepoPath(repo) if err != nil { return helper.ErrInternalf("getting repo path: %v", err) @@ -84,24 +84,26 @@ func (m *GitLabHookManager) preReceiveHook(ctx context.Context, repo *gitalypb.R return helper.ErrInternalf("hook got no reference updates") } - glID, glRepo, glProtocol := getEnvVar("GL_ID", env), getEnvVar("GL_REPOSITORY", env), getEnvVar("GL_PROTOCOL", env) - if glID == "" { - return helper.ErrInternalf("GL_ID not set") + if repo.GetGlRepository() == "" { + return helper.ErrInternalf("repository not set") } - if glRepo == "" { - return helper.ErrInternalf("GL_REPOSITORY not set") + if payload.ReceiveHooksPayload == nil { + return helper.ErrInternalf("payload has no receive hooks info") } - if glProtocol == "" { - return helper.ErrInternalf("GL_PROTOCOL not set") + if payload.ReceiveHooksPayload.UserID == "" { + return helper.ErrInternalf("user ID not set") + } + if payload.ReceiveHooksPayload.Protocol == "" { + return helper.ErrInternalf("protocol not set") } params := AllowedParams{ RepoPath: repoPath, GitObjectDirectory: repo.GitObjectDirectory, GitAlternateObjectDirectories: repo.GitAlternateObjectDirectories, - GLRepository: glRepo, - GLID: glID, - GLProtocol: glProtocol, + GLRepository: repo.GetGlRepository(), + GLID: payload.ReceiveHooksPayload.UserID, + GLProtocol: payload.ReceiveHooksPayload.Protocol, Changes: string(changes), } @@ -122,7 +124,7 @@ func (m *GitLabHookManager) preReceiveHook(ctx context.Context, repo *gitalypb.R if err = executor( ctx, nil, - env, + append(env, customHooksEnv(payload)...), bytes.NewReader(changes), stdout, stderr, @@ -131,7 +133,7 @@ func (m *GitLabHookManager) preReceiveHook(ctx context.Context, repo *gitalypb.R } // reference counter - ok, err := m.gitlabAPI.PreReceive(ctx, glRepo) + ok, err := m.gitlabAPI.PreReceive(ctx, repo.GetGlRepository()) if err != nil { return helper.ErrInternalf("calling pre_receive endpoint: %v", err) } diff --git a/internal/gitaly/hook/prereceive_test.go b/internal/gitaly/hook/prereceive_test.go index 7690a220d..de38e0f5c 100644 --- a/internal/gitaly/hook/prereceive_test.go +++ b/internal/gitaly/hook/prereceive_test.go @@ -23,15 +23,16 @@ func TestPrereceive_customHooks(t *testing.T) { hookManager := NewManager(config.NewLocator(config.Config), GitlabAPIStub, config.Config) standardEnv := []string{ - "GL_ID=1234", - fmt.Sprintf("GL_PROJECT_PATH=%s", repo.GetGlProjectPath()), - "GL_PROTOCOL=web", fmt.Sprintf("GL_REPO=%s", repo), - fmt.Sprintf("GL_REPOSITORY=%s", repo.GetGlRepository()), - "GL_USERNAME=user", } - payload, err := git.NewHooksPayload(config.Config, repo, nil, nil).Env() + receiveHooksPayload := &git.ReceiveHooksPayload{ + UserID: "1234", + Username: "user", + Protocol: "web", + } + + payload, err := git.NewHooksPayload(config.Config, repo, nil, nil, receiveHooksPayload).Env() require.NoError(t, err) primaryPayload, err := git.NewHooksPayload( @@ -44,6 +45,7 @@ func TestPrereceive_customHooks(t *testing.T) { SocketPath: "/path/to/socket", Token: "secret", }, + receiveHooksPayload, ).Env() require.NoError(t, err) @@ -57,6 +59,7 @@ func TestPrereceive_customHooks(t *testing.T) { SocketPath: "/path/to/socket", Token: "secret", }, + receiveHooksPayload, ).Env() require.NoError(t, err) @@ -70,11 +73,19 @@ func TestPrereceive_customHooks(t *testing.T) { expectedStderr string }{ { - desc: "hook receives environment variables", - env: append(standardEnv, payload), - hook: "#!/bin/sh\nenv | grep -e '^GL_' -e '^GITALY_' | sort\n", - stdin: "change\n", - expectedStdout: strings.Join(append([]string{payload}, standardEnv...), "\n") + "\n", + desc: "hook receives environment variables", + env: append(standardEnv, payload), + hook: "#!/bin/sh\nenv | grep -e '^GL_' -e '^GITALY_' | sort\n", + stdin: "change\n", + expectedStdout: strings.Join([]string{ + payload, + "GL_ID=1234", + fmt.Sprintf("GL_PROJECT_PATH=%s", repo.GetGlProjectPath()), + "GL_PROTOCOL=web", + fmt.Sprintf("GL_REPO=%s", repo), + fmt.Sprintf("GL_REPOSITORY=%s", repo.GetGlRepository()), + "GL_USERNAME=user", + }, "\n") + "\n", }, { desc: "hook can write to stderr and stdout", @@ -126,24 +137,6 @@ func TestPrereceive_customHooks(t *testing.T) { stdin: "change\n", }, { - desc: "missing GL_ID causes error", - env: envWithout(append(standardEnv, payload), "GL_ID"), - stdin: "change\n", - expectedErr: "GL_ID not set", - }, - { - desc: "missing GL_REPOSITORY causes error", - env: envWithout(append(standardEnv, payload), "GL_REPOSITORY"), - stdin: "change\n", - expectedErr: "GL_REPOSITORY not set", - }, - { - desc: "missing GL_PROTOCOL causes error", - env: envWithout(append(standardEnv, payload), "GL_PROTOCOL"), - stdin: "change\n", - expectedErr: "GL_PROTOCOL not set", - }, - { desc: "missing changes cause error", env: append(standardEnv, payload), expectedErr: "hook got no reference updates", @@ -199,17 +192,16 @@ func TestPrereceive_gitlab(t *testing.T) { testRepo, testRepoPath, cleanup := testhelper.NewTestRepo(t) defer cleanup() - payload, err := git.NewHooksPayload(config.Config, testRepo, nil, nil).Env() + payload, err := git.NewHooksPayload(config.Config, testRepo, nil, nil, &git.ReceiveHooksPayload{ + UserID: "1234", + Username: "user", + Protocol: "web", + }).Env() require.NoError(t, err) standardEnv := []string{ payload, - "GL_ID=1234", - fmt.Sprintf("GL_PROJECT_PATH=%s", testRepo.GetGlProjectPath()), - "GL_PROTOCOL=web", fmt.Sprintf("GL_REPO=%s", testRepo), - fmt.Sprintf("GL_REPOSITORY=%s", testRepo.GetGlRepository()), - "GL_USERNAME=user", } testCases := []struct { diff --git a/internal/gitaly/hook/update.go b/internal/gitaly/hook/update.go index d887f2eca..75dec09dd 100644 --- a/internal/gitaly/hook/update.go +++ b/internal/gitaly/hook/update.go @@ -28,6 +28,9 @@ func (m *GitLabHookManager) UpdateHook(ctx context.Context, repo *gitalypb.Repos if err := git.ValidateCommitID(newValue); err != nil { return helper.ErrInternalf("hook got invalid new value: %w", err) } + if payload.ReceiveHooksPayload == nil { + return helper.ErrInternalf("payload has no receive hooks info") + } executor, err := m.newCustomHooksExecutor(repo, "update") if err != nil { @@ -37,7 +40,7 @@ func (m *GitLabHookManager) UpdateHook(ctx context.Context, repo *gitalypb.Repos if err = executor( ctx, []string{ref, oldValue, newValue}, - env, + append(env, customHooksEnv(payload)...), nil, stdout, stderr, diff --git a/internal/gitaly/hook/update_test.go b/internal/gitaly/hook/update_test.go index 046d66500..acae50b01 100644 --- a/internal/gitaly/hook/update_test.go +++ b/internal/gitaly/hook/update_test.go @@ -20,15 +20,16 @@ func TestUpdate_customHooks(t *testing.T) { hookManager := NewManager(config.NewLocator(config.Config), GitlabAPIStub, config.Config) standardEnv := []string{ - "GL_ID=1234", - fmt.Sprintf("GL_PROJECT_PATH=%s", repo.GetGlProjectPath()), - "GL_PROTOCOL=web", fmt.Sprintf("GL_REPO=%s", repo), - fmt.Sprintf("GL_REPOSITORY=%s", repo.GetGlRepository()), - "GL_USERNAME=user", } - payload, err := git.NewHooksPayload(config.Config, repo, nil, nil).Env() + receiveHooksPayload := &git.ReceiveHooksPayload{ + UserID: "1234", + Username: "user", + Protocol: "web", + } + + payload, err := git.NewHooksPayload(config.Config, repo, nil, nil, receiveHooksPayload).Env() require.NoError(t, err) primaryPayload, err := git.NewHooksPayload( @@ -41,6 +42,7 @@ func TestUpdate_customHooks(t *testing.T) { SocketPath: "/path/to/socket", Token: "secret", }, + receiveHooksPayload, ).Env() require.NoError(t, err) @@ -54,6 +56,7 @@ func TestUpdate_customHooks(t *testing.T) { SocketPath: "/path/to/socket", Token: "secret", }, + receiveHooksPayload, ).Env() require.NoError(t, err) @@ -72,13 +75,21 @@ func TestUpdate_customHooks(t *testing.T) { expectedStderr string }{ { - desc: "hook receives environment variables", - env: append(standardEnv, payload), - reference: "refs/heads/master", - oldHash: hash1, - newHash: hash2, - hook: "#!/bin/sh\nenv | grep -e '^GL_' -e '^GITALY_' | sort\n", - expectedStdout: strings.Join(append([]string{payload}, standardEnv...), "\n") + "\n", + desc: "hook receives environment variables", + env: append(standardEnv, payload), + reference: "refs/heads/master", + oldHash: hash1, + newHash: hash2, + hook: "#!/bin/sh\nenv | grep -e '^GL_' -e '^GITALY_' | sort\n", + expectedStdout: strings.Join([]string{ + payload, + "GL_ID=1234", + fmt.Sprintf("GL_PROJECT_PATH=%s", repo.GetGlProjectPath()), + "GL_PROTOCOL=web", + fmt.Sprintf("GL_REPO=%s", repo), + fmt.Sprintf("GL_REPOSITORY=%s", repo.GetGlRepository()), + "GL_USERNAME=user", + }, "\n") + "\n", }, { desc: "hook receives arguments", diff --git a/internal/gitaly/service/hook/post_receive_test.go b/internal/gitaly/service/hook/post_receive_test.go index 566db311b..75426a98a 100644 --- a/internal/gitaly/service/hook/post_receive_test.go +++ b/internal/gitaly/service/hook/post_receive_test.go @@ -51,7 +51,7 @@ func TestHooksMissingStdin(t *testing.T) { Password: password, SecretToken: secretToken, GLID: "key_id", - GLRepository: "repository", + GLRepository: testRepo.GetGlRepository(), Changes: "changes", PostReceiveCounterDecreased: true, Protocol: "protocol", @@ -118,6 +118,11 @@ func TestHooksMissingStdin(t *testing.T) { SocketPath: "/path/to/socket", Token: "secret", }, + &git.ReceiveHooksPayload{ + UserID: "key_id", + Username: "username", + Protocol: "protocol", + }, ).Env() require.NoError(t, err) @@ -127,10 +132,6 @@ func TestHooksMissingStdin(t *testing.T) { Repository: testRepo, EnvironmentVariables: []string{ hooksPayload, - "GL_ID=key_id", - "GL_USERNAME=username", - "GL_PROTOCOL=protocol", - "GL_REPOSITORY=repository", }, })) @@ -206,7 +207,7 @@ To create a merge request for okay, visit: Password: password, SecretToken: secretToken, GLID: "key_id", - GLRepository: "repository", + GLRepository: testRepo.GetGlRepository(), Changes: "changes", PostReceiveCounterDecreased: true, PostReceiveMessages: tc.basicMessages, @@ -248,15 +249,15 @@ To create a merge request for okay, visit: stream, err := client.PostReceiveHook(ctx) require.NoError(t, err) - hooksPayload, err := git.NewHooksPayload(config.Config, testRepo, nil, nil).Env() + hooksPayload, err := git.NewHooksPayload(config.Config, testRepo, nil, nil, &git.ReceiveHooksPayload{ + UserID: "key_id", + Username: "username", + Protocol: "protocol", + }).Env() require.NoError(t, err) envVars := []string{ hooksPayload, - "GL_ID=key_id", - "GL_USERNAME=username", - "GL_PROTOCOL=protocol", - "GL_REPOSITORY=repository", } require.NoError(t, stream.Send(&gitalypb.PostReceiveHookRequest{ diff --git a/internal/gitaly/service/hook/pre_receive_test.go b/internal/gitaly/service/hook/pre_receive_test.go index 4f95915ad..eb9514dce 100644 --- a/internal/gitaly/service/hook/pre_receive_test.go +++ b/internal/gitaly/service/hook/pre_receive_test.go @@ -83,7 +83,7 @@ func receivePreReceive(t *testing.T, stream gitalypb.HookService_PreReceiveHookC func TestPreReceiveHook_GitlabAPIAccess(t *testing.T) { user, password := "user", "password" secretToken := "secret123" - glID, glRepository := "key-123", "repository" + glID := "key-123" changes := "changes123" protocol := "http" testRepo, testRepoPath, cleanupFn := testhelper.NewTestRepo(t) @@ -112,7 +112,7 @@ func TestPreReceiveHook_GitlabAPIAccess(t *testing.T) { Password: password, SecretToken: secretToken, GLID: glID, - GLRepository: glRepository, + GLRepository: testRepo.GetGlRepository(), Changes: changes, PostReceiveCounterDecreased: true, Protocol: protocol, @@ -145,7 +145,11 @@ func TestPreReceiveHook_GitlabAPIAccess(t *testing.T) { ctx, cancel := testhelper.Context() defer cancel() - hooksPayload, err := git.NewHooksPayload(config.Config, testRepo, nil, nil).Env() + hooksPayload, err := git.NewHooksPayload(config.Config, testRepo, nil, nil, &git.ReceiveHooksPayload{ + UserID: glID, + Username: "username", + Protocol: protocol, + }).Env() require.NoError(t, err) stdin := bytes.NewBufferString(changes) @@ -153,10 +157,6 @@ func TestPreReceiveHook_GitlabAPIAccess(t *testing.T) { Repository: testRepo, EnvironmentVariables: []string{ hooksPayload, - "GL_ID=" + glID, - "GL_PROTOCOL=" + protocol, - "GL_USERNAME=username", - "GL_REPOSITORY=" + glRepository, }, } @@ -252,7 +252,11 @@ func TestPreReceive_APIErrors(t *testing.T) { ctx, cancel := testhelper.Context() defer cancel() - hooksPayload, err := git.NewHooksPayload(config.Config, testRepo, nil, nil).Env() + hooksPayload, err := git.NewHooksPayload(config.Config, testRepo, nil, nil, &git.ReceiveHooksPayload{ + UserID: "key-123", + Username: "username", + Protocol: "web", + }).Env() require.NoError(t, err) stream, err := client.PreReceiveHook(ctx) @@ -261,10 +265,6 @@ func TestPreReceive_APIErrors(t *testing.T) { Repository: testRepo, EnvironmentVariables: []string{ hooksPayload, - "GL_ID=key-123", - "GL_PROTOCOL=web", - "GL_USERNAME=username", - "GL_REPOSITORY=repository", }, })) require.NoError(t, stream.Send(&gitalypb.PreReceiveHookRequest{ @@ -319,7 +319,11 @@ exit %d ctx, cancel := testhelper.Context() defer cancel() - hooksPayload, err := git.NewHooksPayload(config.Config, testRepo, nil, nil).Env() + hooksPayload, err := git.NewHooksPayload(config.Config, testRepo, nil, nil, &git.ReceiveHooksPayload{ + UserID: "key-123", + Username: "username", + Protocol: "web", + }).Env() require.NoError(t, err) stream, err := client.PreReceiveHook(ctx) @@ -328,10 +332,6 @@ exit %d Repository: testRepo, EnvironmentVariables: []string{ hooksPayload, - "GL_ID=key-123", - "GL_PROTOCOL=web", - "GL_USERNAME=username", - "GL_REPOSITORY=repository", }, })) require.NoError(t, stream.Send(&gitalypb.PreReceiveHookRequest{ @@ -455,15 +455,16 @@ func TestPreReceiveHook_Primary(t *testing.T) { SocketPath: "/path/to/socket", Token: "secret", }, + &git.ReceiveHooksPayload{ + UserID: "key-123", + Username: "username", + Protocol: "web", + }, ).Env() require.NoError(t, err) environment := []string{ hooksPayload, - "GL_ID=key-123", - "GL_PROTOCOL=web", - "GL_USERNAME=username", - "GL_REPOSITORY=repository", } stream, err := client.PreReceiveHook(ctx) diff --git a/internal/gitaly/service/hook/reference_transaction_test.go b/internal/gitaly/service/hook/reference_transaction_test.go index 3e08d6a32..e7c4b660b 100644 --- a/internal/gitaly/service/hook/reference_transaction_test.go +++ b/internal/gitaly/service/hook/reference_transaction_test.go @@ -134,6 +134,7 @@ func TestReferenceTransactionHook(t *testing.T) { &metadata.PraefectServer{ ListenAddr: "tcp://" + listener.Addr().String(), }, + nil, ).Env() require.NoError(t, err) diff --git a/internal/gitaly/service/hook/update_test.go b/internal/gitaly/service/hook/update_test.go index d8ceb9b7d..e7c266982 100644 --- a/internal/gitaly/service/hook/update_test.go +++ b/internal/gitaly/service/hook/update_test.go @@ -44,15 +44,15 @@ func TestUpdate_CustomHooks(t *testing.T) { client, conn := newHooksClient(t, serverSocketPath) defer conn.Close() - hooksPayload, err := git.NewHooksPayload(config.Config, testRepo, nil, nil).Env() + hooksPayload, err := git.NewHooksPayload(config.Config, testRepo, nil, nil, &git.ReceiveHooksPayload{ + UserID: "key-123", + Username: "username", + Protocol: "web", + }).Env() require.NoError(t, err) envVars := []string{ hooksPayload, - "GL_ID=key-123", - "GL_USERNAME=username", - "GL_PROTOCOL=protocol", - "GL_REPOSITORY=repository", } ctx, cancel := testhelper.Context() diff --git a/internal/gitaly/service/operations/update_with_hooks.go b/internal/gitaly/service/operations/update_with_hooks.go index 134466236..ba90634bc 100644 --- a/internal/gitaly/service/operations/update_with_hooks.go +++ b/internal/gitaly/service/operations/update_with_hooks.go @@ -36,7 +36,11 @@ func (s *Server) updateReferenceWithHooks(ctx context.Context, repo *gitalypb.Re return err } - payload, err := git.NewHooksPayload(s.cfg, repo, transaction, praefect).Env() + payload, err := git.NewHooksPayload(s.cfg, repo, transaction, praefect, &git.ReceiveHooksPayload{ + UserID: user.GetGlId(), + Username: user.GetGlUsername(), + Protocol: "web", + }).Env() if err != nil { return err } @@ -53,11 +57,6 @@ func (s *Server) updateReferenceWithHooks(ctx context.Context, repo *gitalypb.Re env := []string{ payload, - "GL_PROTOCOL=web", - fmt.Sprintf("GL_ID=%s", user.GetGlId()), - fmt.Sprintf("GL_USERNAME=%s", user.GetGlUsername()), - fmt.Sprintf("GL_REPOSITORY=%s", repo.GetGlRepository()), - fmt.Sprintf("GL_PROJECT_PATH=%s", repo.GetGlProjectPath()), } changes := fmt.Sprintf("%s %s %s\n", oldrev, newrev, reference) diff --git a/internal/gitaly/service/operations/update_with_hooks_test.go b/internal/gitaly/service/operations/update_with_hooks_test.go index ecd480e90..87ad136fd 100644 --- a/internal/gitaly/service/operations/update_with_hooks_test.go +++ b/internal/gitaly/service/operations/update_with_hooks_test.go @@ -131,16 +131,15 @@ func TestUpdateReferenceWithHooks(t *testing.T) { repo, repoPath, cleanup := testhelper.NewTestRepo(t) defer cleanup() - payload, err := git.NewHooksPayload(config.Config, repo, nil, nil).Env() + payload, err := git.NewHooksPayload(config.Config, repo, nil, nil, &git.ReceiveHooksPayload{ + UserID: "1234", + Username: "Username", + Protocol: "web", + }).Env() require.NoError(t, err) expectedEnv := []string{ payload, - "GL_ID=1234", - "GL_PROJECT_PATH=gitlab-org/gitlab-test", - "GL_PROTOCOL=web", - "GL_REPOSITORY=project-1", - "GL_USERNAME=Username", } testCases := []struct { @@ -158,21 +157,21 @@ func TestUpdateReferenceWithHooks(t *testing.T) { changes, err := ioutil.ReadAll(stdin) require.NoError(t, err) require.Equal(t, fmt.Sprintf("%s %s refs/heads/master\n", oldRev, git.NullSHA), string(changes)) - require.Subset(t, env, expectedEnv) + require.Equal(t, env, expectedEnv) return nil }, update: func(t *testing.T, ctx context.Context, repo *gitalypb.Repository, ref, oldValue, newValue string, env []string, stdout, stderr io.Writer) error { require.Equal(t, "refs/heads/master", ref) require.Equal(t, oldRev, oldValue) require.Equal(t, newValue, git.NullSHA) - require.Subset(t, env, expectedEnv) + require.Equal(t, env, expectedEnv) return nil }, postReceive: func(t *testing.T, ctx context.Context, repo *gitalypb.Repository, pushOptions, env []string, stdin io.Reader, stdout, stderr io.Writer) error { changes, err := ioutil.ReadAll(stdin) require.NoError(t, err) require.Equal(t, fmt.Sprintf("%s %s refs/heads/master\n", oldRev, git.NullSHA), string(changes)) - require.Subset(t, env, expectedEnv) + require.Equal(t, env, expectedEnv) require.Empty(t, pushOptions) return nil }, @@ -181,7 +180,7 @@ func TestUpdateReferenceWithHooks(t *testing.T) { require.NoError(t, err) require.Equal(t, fmt.Sprintf("%s %s refs/heads/master\n", oldRev, git.NullSHA), string(changes)) require.Equal(t, state, hook.ReferenceTransactionPrepared) - require.Subset(t, env, expectedEnv) + require.Equal(t, env, expectedEnv) return nil }, expectedRefDeletion: true, diff --git a/internal/gitaly/service/smarthttp/receive_pack_test.go b/internal/gitaly/service/smarthttp/receive_pack_test.go index 36acf19f7..888911d45 100644 --- a/internal/gitaly/service/smarthttp/receive_pack_test.go +++ b/internal/gitaly/service/smarthttp/receive_pack_test.go @@ -61,7 +61,7 @@ func TestSuccessfulReceivePackRequest(t *testing.T) { projectPath := "project/path" repo.GlProjectPath = projectPath - firstRequest := &gitalypb.PostReceivePackRequest{Repository: repo, GlId: "user-123", GlRepository: "project-456"} + firstRequest := &gitalypb.PostReceivePackRequest{Repository: repo, GlUsername: "user", GlId: "123", GlRepository: "project-456"} response := doPush(t, stream, firstRequest, push.body) expectedResponse := "0049\x01000eunpack ok\n0019ok refs/heads/master\n0019ok refs/heads/branch\n00000000" @@ -73,14 +73,30 @@ func TestSuccessfulReceivePackRequest(t *testing.T) { envData, err := ioutil.ReadFile(hookOutputFile) require.NoError(t, err, "get git env data") - for _, env := range []string{ - "GL_ID=user-123", - "GL_REPOSITORY=project-456", - "GL_PROTOCOL=http", - "GL_PROJECT_PATH=" + projectPath, - } { - require.Contains(t, strings.Split(string(envData), "\n"), env) - } + payload, err := git.HooksPayloadFromEnv(strings.Split(string(envData), "\n")) + require.NoError(t, err) + + // Compare the repository up front so that we can use require.Equal for + // the remaining values. + testhelper.ProtoEqual(t, repo, payload.Repo) + payload.Repo = nil + + // If running tests with Praefect, then these would be set, but we have + // no way of figuring out their actual contents. So let's just remove + // that data, too. + payload.Transaction = nil + payload.Praefect = nil + + require.Equal(t, git.HooksPayload{ + BinDir: config.Config.BinDir, + InternalSocket: config.Config.GitalyInternalSocketPath(), + InternalSocketToken: config.Config.Auth.Token, + ReceiveHooksPayload: &git.ReceiveHooksPayload{ + UserID: "123", + Username: "user", + Protocol: "http", + }, + }, payload) } func TestSuccessfulReceivePackRequestWithGitProtocol(t *testing.T) { diff --git a/internal/gitaly/service/ssh/receive_pack_test.go b/internal/gitaly/service/ssh/receive_pack_test.go index c7c66266d..7f66ab142 100644 --- a/internal/gitaly/service/ssh/receive_pack_test.go +++ b/internal/gitaly/service/ssh/receive_pack_test.go @@ -91,7 +91,13 @@ func TestReceivePackPushSuccess(t *testing.T) { glRepository := "project-456" glProjectPath := "project/path" - lHead, rHead, err := testCloneAndPush(t, serverSocketPath, pushParams{storageName: testhelper.DefaultStorageName, glID: "user-123", glRepository: glRepository, glProjectPath: glProjectPath}) + lHead, rHead, err := testCloneAndPush(t, serverSocketPath, pushParams{ + storageName: testhelper.DefaultStorageName, + glID: "123", + glUsername: "user", + glRepository: glRepository, + glProjectPath: glProjectPath, + }) if err != nil { t.Fatal(err) } @@ -100,14 +106,35 @@ func TestReceivePackPushSuccess(t *testing.T) { envData, err := ioutil.ReadFile(hookOutputFile) require.NoError(t, err, "get git env data") - for _, env := range []string{ - "GL_ID=user-123", - fmt.Sprintf("GL_REPOSITORY=%s", glRepository), - fmt.Sprintf("GL_PROJECT_PATH=%s", glProjectPath), - "GL_PROTOCOL=ssh", - } { - require.Contains(t, strings.Split(string(envData), "\n"), env) - } + payload, err := git.HooksPayloadFromEnv(strings.Split(string(envData), "\n")) + require.NoError(t, err) + + // Compare the repository up front so that we can use require.Equal for + // the remaining values. + testhelper.ProtoEqual(t, &gitalypb.Repository{ + StorageName: testhelper.DefaultStorageName, + RelativePath: "gitlab-test-ssh-receive-pack.git", + GlProjectPath: glProjectPath, + GlRepository: glRepository, + }, payload.Repo) + payload.Repo = nil + + // If running tests with Praefect, then these would be set, but we have + // no way of figuring out their actual contents. So let's just remove + // that data, too. + payload.Transaction = nil + payload.Praefect = nil + + require.Equal(t, git.HooksPayload{ + BinDir: config.Config.BinDir, + InternalSocket: config.Config.GitalyInternalSocketPath(), + InternalSocketToken: config.Config.Auth.Token, + ReceiveHooksPayload: &git.ReceiveHooksPayload{ + UserID: "123", + Username: "user", + Protocol: "ssh", + }, + }, payload) } func TestReceivePackPushSuccessWithGitProtocol(t *testing.T) { @@ -313,12 +340,18 @@ func setupSSHClone(t *testing.T) (SSHCloneDetails, func()) { } func sshPush(t *testing.T, cloneDetails SSHCloneDetails, serverSocketPath string, params pushParams) (string, string, error) { - pbTempRepo := &gitalypb.Repository{StorageName: params.storageName, RelativePath: cloneDetails.TempRepo, GlProjectPath: params.glProjectPath} + pbTempRepo := &gitalypb.Repository{ + StorageName: params.storageName, + RelativePath: cloneDetails.TempRepo, + GlProjectPath: params.glProjectPath, + GlRepository: params.glRepository, + } pbMarshaler := &jsonpb.Marshaler{} payload, err := pbMarshaler.MarshalToString(&gitalypb.SSHReceivePackRequest{ Repository: pbTempRepo, GlRepository: params.glRepository, GlId: params.glID, + GlUsername: params.glUsername, GitConfigOptions: params.gitConfigOptions, GitProtocol: params.gitProtocol, }) @@ -392,6 +425,7 @@ func drainPostReceivePackResponse(stream gitalypb.SSHService_SSHReceivePackClien type pushParams struct { storageName string glID string + glUsername string glRepository string glProjectPath string gitConfigOptions []string diff --git a/ruby/lib/gitlab/git/hook.rb b/ruby/lib/gitlab/git/hook.rb index d45342974..07dc0c8a4 100644 --- a/ruby/lib/gitlab/git/hook.rb +++ b/ruby/lib/gitlab/git/hook.rb @@ -109,12 +109,17 @@ module Gitlab err_message end - def hooks_payload(transaction) + def hooks_payload(gl_id, gl_username, transaction) payload = { repository: repository.gitaly_repository.to_json, binary_directory: Gitlab.config.gitaly.bin_dir, internal_socket: Gitlab.config.gitaly.internal_socket, - internal_socket_token: ENV['GITALY_TOKEN'] + internal_socket_token: ENV['GITALY_TOKEN'], + receive_hooks_payload: { + userid: gl_id, + username: gl_username, + protocol: GL_PROTOCOL + } } payload.merge!(transaction.payload) if transaction @@ -124,14 +129,9 @@ module Gitlab def env_base_vars(gl_id, gl_username, transaction = nil) { - 'GITALY_HOOKS_PAYLOAD' => hooks_payload(transaction), + 'GITALY_HOOKS_PAYLOAD' => hooks_payload(gl_id, gl_username, transaction), 'GITALY_LOG_DIR' => Gitlab.config.logging.dir, 'GITALY_BIN_DIR' => Gitlab.config.gitaly.bin_dir, - 'GL_ID' => gl_id, - 'GL_USERNAME' => gl_username, - 'GL_REPOSITORY' => repository.gl_repository, - 'GL_PROJECT_PATH' => repository.gl_project_path, - 'GL_PROTOCOL' => GL_PROTOCOL, 'PWD' => repo_path, 'GIT_DIR' => repo_path } diff --git a/ruby/spec/lib/gitlab/git/hook_spec.rb b/ruby/spec/lib/gitlab/git/hook_spec.rb index 1abfd1755..133051b5e 100644 --- a/ruby/spec/lib/gitlab/git/hook_spec.rb +++ b/ruby/spec/lib/gitlab/git/hook_spec.rb @@ -38,10 +38,17 @@ describe Gitlab::Git::Hook do context 'when the hooks require environment variables' do let(:vars) do { - 'GL_ID' => 'user-123', - 'GL_USERNAME' => 'janedoe', - 'GL_REPOSITORY' => repo.gl_repository, - 'GL_PROTOCOL' => 'web', + 'GITALY_HOOKS_PAYLOAD' => Base64.strict_encode64({ + repository: repo.gitaly_repository.to_json, + binary_directory: Gitlab.config.gitaly.bin_dir, + internal_socket: Gitlab.config.gitaly.internal_socket, + internal_socket_token: nil, + receive_hooks_payload: { + userid: 'user-123', + username: 'janedoe', + protocol: 'web' + } + }.to_json), 'PWD' => repo.path, 'GIT_DIR' => repo.path } @@ -65,7 +72,7 @@ describe Gitlab::Git::Hook do it 'returns true' do hook_names.each do |hook| trigger_result = described_class.new(hook, repo) - .trigger(vars['GL_ID'], vars['GL_USERNAME'], '0' * 40, 'a' * 40, 'master', push_options: push_options) + .trigger('user-123', 'janedoe', '0' * 40, 'a' * 40, 'master', push_options: push_options) expect(trigger_result.first).to be(true), "#{hook} failed: #{trigger_result.last}" end |