From b9aabc183bd4a6aa42732e24dbb7b662f9edda66 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 12 Feb 2021 07:54:57 +0100 Subject: gitaly-hooks: Pull hook logic into their own functions By now, we support five different hooks with non-trivial logic, but they're all open-coded in the statement switching over the subcommand. This makes it rather hard to refactor and extend the code, so this commit moves the logic into per-hook functions. No functional changes are expected from this commit. --- cmd/gitaly-hooks/hooks.go | 275 ++++++++++++++++++++++++++-------------------- 1 file changed, 155 insertions(+), 120 deletions(-) (limited to 'cmd/gitaly-hooks') diff --git a/cmd/gitaly-hooks/hooks.go b/cmd/gitaly-hooks/hooks.go index 416936a22..d2663b109 100644 --- a/cmd/gitaly-hooks/hooks.go +++ b/cmd/gitaly-hooks/hooks.go @@ -80,135 +80,26 @@ func main() { hookClient := gitalypb.NewHookServiceClient(conn) - hookStatus := int32(1) - + var returnCode int switch subCmd { case "update": - args := os.Args[2:] - if len(args) != 3 { - logger.Fatalf("hook %q expects exactly three arguments", subCmd) - } - ref, oldValue, newValue := args[0], args[1], args[2] - - req := &gitalypb.UpdateHookRequest{ - Repository: payload.Repo, - EnvironmentVariables: os.Environ(), - Ref: []byte(ref), - OldValue: oldValue, - NewValue: newValue, - } - - updateHookStream, err := hookClient.UpdateHook(ctx, req) - if err != nil { - logger.Fatalf("error when starting command for %q: %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 %q: %v", subCmd, err) - } + returnCode, err = updateHook(ctx, payload, hookClient, os.Args) case "pre-receive": - preReceiveHookStream, err := hookClient.PreReceiveHook(ctx) - if err != nil { - logger.Fatalf("error when getting preReceiveHookStream client for %q: %v", subCmd, err) - } - - if err := preReceiveHookStream.Send(&gitalypb.PreReceiveHookRequest{ - Repository: payload.Repo, - EnvironmentVariables: os.Environ(), - GitPushOptions: gitPushOptions(), - }); err != nil { - logger.Fatalf("error when sending request for %q: %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 %q: %v", subCmd, err) - } + returnCode, err = preReceiveHook(ctx, payload, hookClient, os.Args) case "post-receive": - postReceiveHookStream, err := hookClient.PostReceiveHook(ctx) - if err != nil { - logger.Fatalf("error when getting stream client for %q: %v", subCmd, err) - } - - if err := postReceiveHookStream.Send(&gitalypb.PostReceiveHookRequest{ - Repository: payload.Repo, - EnvironmentVariables: os.Environ(), - GitPushOptions: gitPushOptions(), - }); err != nil { - logger.Fatalf("error when sending request for %q: %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 %q: %v", subCmd, err) - } + returnCode, err = postReceiveHook(ctx, payload, hookClient, os.Args) case "reference-transaction": - if len(os.Args) != 3 { - logger.Fatalf("hook %q is missing required arguments", subCmd) - } - - var state gitalypb.ReferenceTransactionHookRequest_State - switch os.Args[2] { - case "prepared": - state = gitalypb.ReferenceTransactionHookRequest_PREPARED - case "committed": - state = gitalypb.ReferenceTransactionHookRequest_COMMITTED - case "aborted": - state = gitalypb.ReferenceTransactionHookRequest_ABORTED - default: - logger.Fatalf("hook %q has invalid state %s", subCmd, os.Args[2]) - } - - referenceTransactionHookStream, err := hookClient.ReferenceTransactionHook(ctx) - if err != nil { - logger.Fatalf("error when getting referenceTransactionHookStream client for %q: %v", subCmd, err) - } - - if err := referenceTransactionHookStream.Send(&gitalypb.ReferenceTransactionHookRequest{ - Repository: payload.Repo, - EnvironmentVariables: os.Environ(), - State: state, - }); err != nil { - logger.Fatalf("error when sending request for %q: %v", subCmd, err) - } - - f := sendFunc(streamio.NewWriter(func(p []byte) error { - return referenceTransactionHookStream.Send(&gitalypb.ReferenceTransactionHookRequest{Stdin: p}) - }), referenceTransactionHookStream, os.Stdin) - - if hookStatus, err = stream.Handler(func() (stream.StdoutStderrResponse, error) { - return referenceTransactionHookStream.Recv() - }, f, os.Stdout, os.Stderr); err != nil { - logger.Fatalf("error when receiving data for %q: %v", subCmd, err) - } + returnCode, err = referenceTransactionHook(ctx, payload, hookClient, os.Args) case "git": - var args []string - for _, a := range os.Args[2:] { - args = append(args, fixFilterQuoteBug(a)) - } - - hookStatus = 0 - if err := handlePackObjects(ctx, hookClient, payload.Repo, args); err != nil { - hookStatus = 1 - logger.Logger().WithFields(logrus.Fields{"args": args}).WithError(err).Error("PackObjectsHook RPC failed") - } + returnCode, err = packObjectsHook(ctx, payload, hookClient, os.Args) default: - logger.Fatalf("subcommand name invalid: %q", subCmd) + returnCode, err = 1, fmt.Errorf("subcommand name invalid: %q", subCmd) + } + if err != nil { + logger.Fatal(err) } - os.Exit(int(hookStatus)) + os.Exit(returnCode) } func noopSender(c chan error) {} @@ -270,6 +161,150 @@ func check(configPath string) (*hook.CheckInfo, error) { return hook.NewManager(config.NewLocator(cfg), nil, gitlabAPI, cfg).Check(context.TODO()) } +func updateHook(ctx context.Context, payload git.HooksPayload, hookClient gitalypb.HookServiceClient, args []string) (int, error) { + args = args[2:] + if len(args) != 3 { + return 1, errors.New("update hook expects exactly three arguments") + } + ref, oldValue, newValue := args[0], args[1], args[2] + + req := &gitalypb.UpdateHookRequest{ + Repository: payload.Repo, + EnvironmentVariables: os.Environ(), + Ref: []byte(ref), + OldValue: oldValue, + NewValue: newValue, + } + + updateHookStream, err := hookClient.UpdateHook(ctx, req) + if err != nil { + return 1, fmt.Errorf("error when starting command for update hook: %v", err) + } + + var returnCode int32 + if returnCode, err = stream.Handler(func() (stream.StdoutStderrResponse, error) { + return updateHookStream.Recv() + }, noopSender, os.Stdout, os.Stderr); err != nil { + return 1, fmt.Errorf("error when receiving data for update hook: %v", err) + } + + return int(returnCode), nil +} + +func preReceiveHook(ctx context.Context, payload git.HooksPayload, hookClient gitalypb.HookServiceClient, args []string) (int, error) { + preReceiveHookStream, err := hookClient.PreReceiveHook(ctx) + if err != nil { + return 1, fmt.Errorf("error when getting preReceiveHookStream client for: %v", err) + } + + if err := preReceiveHookStream.Send(&gitalypb.PreReceiveHookRequest{ + Repository: payload.Repo, + EnvironmentVariables: os.Environ(), + GitPushOptions: gitPushOptions(), + }); err != nil { + return 1, fmt.Errorf("error when sending request for pre-receive hook: %v", err) + } + + f := sendFunc(streamio.NewWriter(func(p []byte) error { + return preReceiveHookStream.Send(&gitalypb.PreReceiveHookRequest{Stdin: p}) + }), preReceiveHookStream, os.Stdin) + + var returnCode int32 + if returnCode, err = stream.Handler(func() (stream.StdoutStderrResponse, error) { + return preReceiveHookStream.Recv() + }, f, os.Stdout, os.Stderr); err != nil { + return 1, fmt.Errorf("error when receiving data for pre-receive hook: %v", err) + } + + return int(returnCode), nil +} + +func postReceiveHook(ctx context.Context, payload git.HooksPayload, hookClient gitalypb.HookServiceClient, args []string) (int, error) { + postReceiveHookStream, err := hookClient.PostReceiveHook(ctx) + if err != nil { + return 1, fmt.Errorf("error when getting stream client for post-receive hook: %v", err) + } + + if err := postReceiveHookStream.Send(&gitalypb.PostReceiveHookRequest{ + Repository: payload.Repo, + EnvironmentVariables: os.Environ(), + GitPushOptions: gitPushOptions(), + }); err != nil { + return 1, fmt.Errorf("error when sending request for post-receive hook: %v", err) + } + + f := sendFunc(streamio.NewWriter(func(p []byte) error { + return postReceiveHookStream.Send(&gitalypb.PostReceiveHookRequest{Stdin: p}) + }), postReceiveHookStream, os.Stdin) + + var returnCode int32 + if returnCode, err = stream.Handler(func() (stream.StdoutStderrResponse, error) { + return postReceiveHookStream.Recv() + }, f, os.Stdout, os.Stderr); err != nil { + return 1, fmt.Errorf("error when receiving data for post-receive hook: %v", err) + } + + return int(returnCode), nil +} + +func referenceTransactionHook(ctx context.Context, payload git.HooksPayload, hookClient gitalypb.HookServiceClient, args []string) (int, error) { + if len(args) != 3 { + return 1, errors.New("reference-transaction hook is missing required arguments") + } + + var state gitalypb.ReferenceTransactionHookRequest_State + switch args[2] { + case "prepared": + state = gitalypb.ReferenceTransactionHookRequest_PREPARED + case "committed": + state = gitalypb.ReferenceTransactionHookRequest_COMMITTED + case "aborted": + state = gitalypb.ReferenceTransactionHookRequest_ABORTED + default: + return 1, fmt.Errorf("reference-transaction hook has invalid state: %q", args[2]) + } + + referenceTransactionHookStream, err := hookClient.ReferenceTransactionHook(ctx) + if err != nil { + return 1, fmt.Errorf("error when getting referenceTransactionHookStream client: %v", err) + } + + if err := referenceTransactionHookStream.Send(&gitalypb.ReferenceTransactionHookRequest{ + Repository: payload.Repo, + EnvironmentVariables: os.Environ(), + State: state, + }); err != nil { + return 1, fmt.Errorf("error when sending request for reference-transaction hook: %v", err) + } + + f := sendFunc(streamio.NewWriter(func(p []byte) error { + return referenceTransactionHookStream.Send(&gitalypb.ReferenceTransactionHookRequest{Stdin: p}) + }), referenceTransactionHookStream, os.Stdin) + + var returnCode int32 + if returnCode, err = stream.Handler(func() (stream.StdoutStderrResponse, error) { + return referenceTransactionHookStream.Recv() + }, f, os.Stdout, os.Stderr); err != nil { + return 1, fmt.Errorf("error when receiving data for reference-transaction hook: %v", err) + } + + return int(returnCode), nil +} + +func packObjectsHook(ctx context.Context, payload git.HooksPayload, hookClient gitalypb.HookServiceClient, args []string) (int, error) { + var fixedArgs []string + for _, a := range args[2:] { + fixedArgs = append(fixedArgs, fixFilterQuoteBug(a)) + } + + if err := handlePackObjects(ctx, hookClient, payload.Repo, fixedArgs); err != nil { + logger.Logger().WithFields(logrus.Fields{"args": args}).WithError(err).Error("PackObjectsHook RPC failed") + return 1, nil + } + + return 0, nil +} + // This is a workaround for a bug in Git: // https://gitlab.com/gitlab-org/git/-/issues/82. Once that bug is fixed // we should no longer need this. The fix function is harmless if the bug -- cgit v1.2.3