1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
package hook
import (
"errors"
"fmt"
"os/exec"
"path/filepath"
"strings"
"gitlab.com/gitlab-org/gitaly/internal/git/hooks"
"gitlab.com/gitlab-org/gitaly/internal/gitaly/config"
"gitlab.com/gitlab-org/gitaly/internal/gitlabshell"
"gitlab.com/gitlab-org/gitaly/internal/helper"
"gitlab.com/gitlab-org/gitaly/internal/praefect/metadata"
"gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
"gitlab.com/gitlab-org/gitaly/streamio"
)
type hookRequest interface {
GetEnvironmentVariables() []string
GetRepository() *gitalypb.Repository
}
type prePostRequest interface {
hookRequest
GetGitPushOptions() []string
}
func hookRequestEnv(req hookRequest) ([]string, error) {
gitlabshellEnv, err := gitlabshell.Env()
if err != nil {
return nil, err
}
return append(gitlabshellEnv, req.GetEnvironmentVariables()...), nil
}
func preReceiveEnv(req prePostRequest) ([]string, error) {
env, err := hookRequestEnv(req)
if err != nil {
return nil, err
}
env = append(env, hooks.GitPushOptions(req.GetGitPushOptions())...)
return env, nil
}
func gitlabShellHook(hookName string) string {
return filepath.Join(config.Config.Ruby.Dir, "gitlab-shell", "hooks", hookName)
}
func isPrimary(env []string) (bool, error) {
tx, err := metadata.TransactionFromEnv(env)
if err != nil {
if errors.Is(err, metadata.ErrTransactionNotFound) {
// If there is no transaction, then we only ever write
// to the primary. Thus, we return true.
return true, nil
}
return false, err
}
return tx.Primary, nil
}
func (s *server) PreReceiveHook(stream gitalypb.HookService_PreReceiveHookServer) error {
firstRequest, err := stream.Recv()
if err != nil {
return helper.ErrInternalf("receiving first request: %w", err)
}
if err := validatePreReceiveHookRequest(firstRequest); err != nil {
return helper.ErrInvalidArgument(err)
}
reqEnvVars := firstRequest.GetEnvironmentVariables()
repository := firstRequest.GetRepository()
stdin := streamio.NewReader(func() ([]byte, error) {
req, err := stream.Recv()
return req.GetStdin(), err
})
stdout := streamio.NewWriter(func(p []byte) error { return stream.Send(&gitalypb.PreReceiveHookResponse{Stdout: p}) })
stderr := streamio.NewWriter(func(p []byte) error { return stream.Send(&gitalypb.PreReceiveHookResponse{Stderr: p}) })
env, err := preReceiveEnv(firstRequest)
if err != nil {
return fmt.Errorf("getting env vars from request: %v", err)
}
if err := s.manager.PreReceiveHook(
stream.Context(),
repository,
append(env, reqEnvVars...),
stdin,
stdout,
stderr,
); err != nil {
var exitError *exec.ExitError
if errors.As(err, &exitError) {
return preReceiveHookResponse(stream, int32(exitError.ExitCode()), "")
}
return preReceiveHookResponse(stream, 1, fmt.Sprintf("%s", err))
}
return preReceiveHookResponse(stream, 0, "")
}
func validatePreReceiveHookRequest(in *gitalypb.PreReceiveHookRequest) error {
if in.GetRepository() == nil {
return errors.New("repository is empty")
}
return nil
}
func preReceiveHookResponse(stream gitalypb.HookService_PreReceiveHookServer, code int32, stderr string) error {
if err := stream.Send(&gitalypb.PreReceiveHookResponse{
ExitStatus: &gitalypb.ExitStatus{Value: code},
Stderr: []byte(stderr),
}); err != nil {
return helper.ErrInternalf("sending response: %v", err)
}
return nil
}
func getEnvVar(key string, vars []string) string {
for _, varPair := range vars {
kv := strings.SplitN(varPair, "=", 2)
if kv[0] == key {
return kv[1]
}
}
return ""
}
|