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>2023-11-13 22:14:32 +0300
committerJohn Cai <jcai@gitlab.com>2023-11-16 21:19:23 +0300
commit848723cc52d0eaec6d992a27bfe58754bc4e5847 (patch)
treecb456ae9ce8470ab08ed70d83c97d6cf95940f7c
parent40b162d825dbc7e1f8c88246b6af47580769e2d6 (diff)
internalgitaly: Implement RunCommand RPCjc/run-command-rpc
Implement the RunCommand RPC, which can be used to execute an arbitrary Git command on a given repository.
-rw-r--r--internal/gitaly/service/internalgitaly/execute_command.go58
-rw-r--r--internal/gitaly/service/internalgitaly/run_command.go62
-rw-r--r--internal/gitaly/service/internalgitaly/run_command_test.go109
-rw-r--r--internal/gitaly/service/internalgitaly/server.go15
4 files changed, 180 insertions, 64 deletions
diff --git a/internal/gitaly/service/internalgitaly/execute_command.go b/internal/gitaly/service/internalgitaly/execute_command.go
deleted file mode 100644
index 278de303e..000000000
--- a/internal/gitaly/service/internalgitaly/execute_command.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package internalgitaly
-
-import (
- "bytes"
- "context"
-
- "gitlab.com/gitlab-org/gitaly/v16/internal/command"
- "gitlab.com/gitlab-org/gitaly/v16/internal/structerr"
- "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb"
-)
-
-func (s *server) ExecuteCommand(
- ctx context.Context,
- req *gitalypb.ExecuteCommandRequest,
-) (*gitalypb.ExecuteCommandResponse, error) {
- repo := req.GetRepository()
-
- if repo == nil {
- return nil, structerr.NewInvalidArgument("repository cannot be empty")
- }
-
- repoPath, err := s.locator.GetRepoPath(repo)
- if err != nil {
- return nil, structerr.NewInternal("error getting repo path %w", err)
- }
-
- var stdout, stderr bytes.Buffer
-
- cmd, err := command.New(
- ctx,
- s.logger,
- req.GetArgs(),
- command.WithStdout(&stdout),
- command.WithStderr(&stderr),
- command.WithDir(repoPath),
- )
- if err != nil {
- return nil, structerr.NewInternal("error creating command: %w", err)
- }
-
- if err := cmd.Wait(); err != nil {
- exitCode, found := command.ExitStatus(err)
- if found {
- return &gitalypb.ExecuteCommandResponse{
- ReturnCode: int32(exitCode),
- Output: stdout.Bytes(),
- ErrorOutput: stderr.Bytes(),
- }, nil
- }
-
- return nil, structerr.NewInternal("error running command: %w", err)
- }
-
- return &gitalypb.ExecuteCommandResponse{
- Output: stdout.Bytes(),
- ErrorOutput: stderr.Bytes(),
- }, nil
-}
diff --git a/internal/gitaly/service/internalgitaly/run_command.go b/internal/gitaly/service/internalgitaly/run_command.go
new file mode 100644
index 000000000..abea2c6ec
--- /dev/null
+++ b/internal/gitaly/service/internalgitaly/run_command.go
@@ -0,0 +1,62 @@
+package internalgitaly
+
+import (
+ "bytes"
+ "context"
+
+ "gitlab.com/gitlab-org/gitaly/v16/internal/command"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/git"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/helper/text"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/structerr"
+ "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb"
+)
+
+func (s *server) RunCommand(
+ ctx context.Context,
+ req *gitalypb.RunCommandRequest,
+) (*gitalypb.RunCommandResponse, error) {
+ repo := req.GetRepository()
+ gitCmdParams := req.GetGitCommand()
+
+ if repo == nil {
+ return nil, structerr.NewInvalidArgument("repository cannot be empty")
+ }
+
+ var flags []git.Option
+
+ for _, flag := range gitCmdParams.GetFlags() {
+ flags = append(flags, &git.Flag{Name: flag})
+ }
+
+ var stdout, stderr bytes.Buffer
+
+ cmd, err := s.gitCmdFactory.New(ctx, repo, git.Command{
+ Name: gitCmdParams.GetName(),
+ Action: gitCmdParams.GetAction(),
+ Flags: flags,
+ Args: gitCmdParams.GetArgs(),
+ PostSepArgs: gitCmdParams.GetPostSeparatorArgs(),
+ }, git.WithStdout(&stdout), git.WithStderr(&stderr),
+ )
+ if err != nil {
+ return nil, structerr.NewInternal("error creating command: %w", err)
+ }
+
+ if err := cmd.Wait(); err != nil {
+ exitCode, found := command.ExitStatus(err)
+ if found {
+ return &gitalypb.RunCommandResponse{
+ ReturnCode: int32(exitCode),
+ Output: stdout.Bytes(),
+ ErrorOutput: stderr.Bytes(),
+ }, nil
+ }
+
+ return nil, structerr.NewInternal("error running command: %w", err)
+ }
+
+ return &gitalypb.RunCommandResponse{
+ Output: []byte(text.ChompBytes(stdout.Bytes())),
+ ErrorOutput: []byte(text.ChompBytes(stderr.Bytes())),
+ }, nil
+}
diff --git a/internal/gitaly/service/internalgitaly/run_command_test.go b/internal/gitaly/service/internalgitaly/run_command_test.go
new file mode 100644
index 000000000..6e56ee7d4
--- /dev/null
+++ b/internal/gitaly/service/internalgitaly/run_command_test.go
@@ -0,0 +1,109 @@
+package internalgitaly
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/git"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/git/gittest"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/git/localrepo"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/config"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/service"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper/testcfg"
+ "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb"
+)
+
+func TestRunCommand(t *testing.T) {
+ t.Parallel()
+
+ ctx := testhelper.Context(t)
+ cfg := testcfg.Build(t)
+
+ testRepo, testRepoPath := gittest.CreateRepository(t, ctx, cfg, gittest.CreateRepositoryConfig{
+ SkipCreationViaService: true,
+ RelativePath: "a",
+ })
+
+ testLocalRepo := localrepo.NewTestRepo(t, cfg, testRepo)
+
+ commitID := gittest.WriteCommit(
+ t,
+ cfg,
+ testRepoPath,
+ gittest.WithMessage("hello world"),
+ gittest.WithBranch(git.DefaultBranch),
+ gittest.WithTreeEntries(
+ gittest.TreeEntry{
+ Mode: "100644",
+ Path: ".gitattributes",
+ Content: "a/b/c foo=bar",
+ },
+ ),
+ )
+ commitData, err := testLocalRepo.ReadObject(ctx, commitID)
+ require.NoError(t, err)
+
+ srv := NewServer(&service.Dependencies{
+ Logger: testhelper.SharedLogger(t),
+ Cfg: cfg,
+ StorageLocator: config.NewLocator(cfg),
+ GitCmdFactory: gittest.NewCommandFactory(t, cfg),
+ })
+
+ client := setupInternalGitalyService(t, cfg, srv)
+
+ testCases := []struct {
+ desc string
+ req *gitalypb.RunCommandRequest
+ expectedExitStatus int32
+ expectedOutput []byte
+ }{
+ {
+ desc: "git cat-file",
+ req: &gitalypb.RunCommandRequest{
+ Repository: testRepo,
+ GitCommand: &gitalypb.GitCommand{
+ Name: "cat-file",
+ Flags: []string{"-p"},
+ Args: []string{git.DefaultBranch},
+ },
+ },
+ expectedExitStatus: 0,
+ expectedOutput: commitData,
+ },
+ {
+ desc: "reading a non-existent object",
+ req: &gitalypb.RunCommandRequest{
+ Repository: testRepo,
+ GitCommand: &gitalypb.GitCommand{
+ Name: "cat-file",
+ Flags: []string{"-p"},
+ Args: []string{"does-not-exist"},
+ },
+ },
+ expectedExitStatus: 128,
+ },
+ {
+ desc: "attributes",
+ req: &gitalypb.RunCommandRequest{
+ Repository: testRepo,
+ GitCommand: &gitalypb.GitCommand{
+ Name: "check-attr",
+ Flags: []string{"--source=HEAD"},
+ Args: []string{"foo"},
+ PostSeparatorArgs: []string{"a/b/c"},
+ },
+ },
+ expectedExitStatus: 0,
+ expectedOutput: []byte("a/b/c: foo: bar"),
+ },
+ }
+
+ for _, tc := range testCases {
+ resp, err := client.RunCommand(ctx, tc.req)
+ require.NoError(t, err)
+ require.Equal(t, tc.expectedExitStatus, resp.ReturnCode)
+ require.Equal(t, tc.expectedOutput, resp.Output)
+ }
+}
diff --git a/internal/gitaly/service/internalgitaly/server.go b/internal/gitaly/service/internalgitaly/server.go
index a275ae252..78cf9cb2e 100644
--- a/internal/gitaly/service/internalgitaly/server.go
+++ b/internal/gitaly/service/internalgitaly/server.go
@@ -1,6 +1,7 @@
package internalgitaly
import (
+ "gitlab.com/gitlab-org/gitaly/v16/internal/git"
"gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/config"
"gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/service"
"gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage"
@@ -10,16 +11,18 @@ import (
type server struct {
gitalypb.UnimplementedInternalGitalyServer
- logger log.Logger
- storages []config.Storage
- locator storage.Locator
+ logger log.Logger
+ storages []config.Storage
+ locator storage.Locator
+ gitCmdFactory git.CommandFactory
}
// NewServer return an instance of the Gitaly service.
func NewServer(deps *service.Dependencies) gitalypb.InternalGitalyServer {
return &server{
- logger: deps.GetLogger(),
- storages: deps.GetCfg().Storages,
- locator: deps.GetLocator(),
+ logger: deps.GetLogger(),
+ storages: deps.GetCfg().Storages,
+ locator: deps.GetLocator(),
+ gitCmdFactory: deps.GetGitCmdFactory(),
}
}