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:
authorJames Fargher <jfargher@gitlab.com>2023-06-26 03:02:35 +0300
committerJames Fargher <jfargher@gitlab.com>2023-06-30 05:39:31 +0300
commitf83c3549169c6637f7a59dc406c1580c3d32e836 (patch)
tree2ce2a2eaebc284fee5f9d5672109273223fa695d
parent536fcd1abf9ea505213496e52ae7fa6801e5776c (diff)
repository service: Implement RestoreRepository
The restore logic itself is already tested with backup.Manager. The tests here are only smoke tests and to ensure that the vanity repository works through praefect.
-rw-r--r--internal/git/gittest/repo.go4
-rw-r--r--internal/gitaly/service/repository/restore_repository.go60
-rw-r--r--internal/gitaly/service/repository/restore_repository_test.go190
3 files changed, 252 insertions, 2 deletions
diff --git a/internal/git/gittest/repo.go b/internal/git/gittest/repo.go
index 7da034ff1..baa30c0ea 100644
--- a/internal/git/gittest/repo.go
+++ b/internal/git/gittest/repo.go
@@ -286,11 +286,11 @@ func RewrittenRepository(tb testing.TB, ctx context.Context, cfg config.Cfg, rep
// BundleRepo creates a bundle of a repository. `patterns` define the bundle contents as per
// `git-rev-list-args`. If there are no patterns then `--all` is assumed.
-func BundleRepo(tb testing.TB, cfg config.Cfg, repoPath, bundlePath string, patterns ...string) {
+func BundleRepo(tb testing.TB, cfg config.Cfg, repoPath, bundlePath string, patterns ...string) []byte {
if len(patterns) == 0 {
patterns = []string{"--all"}
}
- Exec(tb, cfg, append([]string{"-C", repoPath, "bundle", "create", bundlePath}, patterns...)...)
+ return Exec(tb, cfg, append([]string{"-C", repoPath, "bundle", "create", bundlePath}, patterns...)...)
}
// ChecksumRepo calculates the checksum of a repository.
diff --git a/internal/gitaly/service/repository/restore_repository.go b/internal/gitaly/service/repository/restore_repository.go
new file mode 100644
index 000000000..1cc20b2c7
--- /dev/null
+++ b/internal/gitaly/service/repository/restore_repository.go
@@ -0,0 +1,60 @@
+package repository
+
+import (
+ "context"
+ "fmt"
+
+ "gitlab.com/gitlab-org/gitaly/v16/internal/backup"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/structerr"
+ "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb"
+)
+
+func (s *server) RestoreRepository(ctx context.Context, in *gitalypb.RestoreRepositoryRequest) (*gitalypb.RestoreRepositoryResponse, error) {
+ if s.backupSink == nil || s.backupLocator == nil {
+ return nil, structerr.NewFailedPrecondition("restore repository: server-side backups are not configured")
+ }
+ if err := s.validateRestoreRepositoryRequest(in); err != nil {
+ return nil, structerr.NewInvalidArgument("restore repository: %w", err)
+ }
+
+ manager := backup.NewManagerLocal(
+ s.backupSink,
+ s.backupLocator,
+ s.locator,
+ s.gitCmdFactory,
+ s.catfileCache,
+ s.txManager,
+ in.GetBackupId(),
+ )
+
+ if err := manager.Restore(ctx, &backup.RestoreRequest{
+ Repository: in.GetRepository(),
+ VanityRepository: in.GetVanityRepository(),
+ AlwaysCreate: in.GetAlwaysCreate(),
+ }); err != nil {
+ return nil, structerr.NewInternal("restore repository: %w", err)
+ }
+
+ return &gitalypb.RestoreRepositoryResponse{}, nil
+}
+
+func (s *server) validateRestoreRepositoryRequest(in *gitalypb.RestoreRepositoryRequest) error {
+ if in.GetBackupId() == "" {
+ return fmt.Errorf("empty BackupId")
+ }
+
+ if err := s.locator.ValidateRepository(in.GetRepository(),
+ storage.WithSkipRepositoryExistenceCheck(),
+ ); err != nil {
+ return fmt.Errorf("repository: %w", err)
+ }
+
+ if err := s.locator.ValidateRepository(in.GetVanityRepository(),
+ storage.WithSkipStorageExistenceCheck(),
+ ); err != nil {
+ return fmt.Errorf("vanity repository: %w", err)
+ }
+
+ return nil
+}
diff --git a/internal/gitaly/service/repository/restore_repository_test.go b/internal/gitaly/service/repository/restore_repository_test.go
new file mode 100644
index 000000000..ffd527177
--- /dev/null
+++ b/internal/gitaly/service/repository/restore_repository_test.go
@@ -0,0 +1,190 @@
+//go:build !gitaly_test_sha256
+
+package repository
+
+import (
+ "context"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/backup"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/git"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/git/gittest"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/config"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/structerr"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper"
+ "gitlab.com/gitlab-org/gitaly/v16/internal/testhelper/testserver"
+ "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb"
+)
+
+func TestRestoreRepository(t *testing.T) {
+ t.Parallel()
+ ctx := testhelper.Context(t)
+
+ type setupData struct {
+ cfg config.Cfg
+ client gitalypb.RepositoryServiceClient
+ repo *gitalypb.Repository
+ repoPath string
+ backupID string
+ expectedChecksum *git.Checksum
+ }
+
+ for _, tc := range []struct {
+ desc string
+ setup func(t *testing.T, ctx context.Context, backupSink backup.Sink, backupLocator backup.Locator) setupData
+ expectedErr error
+ }{
+ {
+ desc: "success",
+ setup: func(t *testing.T, ctx context.Context, backupSink backup.Sink, backupLocator backup.Locator) setupData {
+ cfg, client := setupRepositoryServiceWithoutRepo(t,
+ testserver.WithBackupSink(backupSink),
+ testserver.WithBackupLocator(backupLocator),
+ )
+
+ _, templateRepoPath := gittest.CreateRepository(t, ctx, cfg)
+ oid := gittest.WriteCommit(t, cfg, templateRepoPath, gittest.WithBranch(git.DefaultBranch))
+ gittest.WriteCommit(t, cfg, templateRepoPath, gittest.WithBranch("feature"), gittest.WithParents(oid))
+ checksum := gittest.ChecksumRepo(t, cfg, templateRepoPath)
+
+ repo, repoPath := gittest.CreateRepository(t, ctx, cfg)
+ step := backupLocator.BeginFull(ctx, repo, "abc123")
+
+ w, err := backupSink.GetWriter(ctx, step.BundlePath)
+ require.NoError(t, err)
+ bundle := gittest.BundleRepo(t, cfg, templateRepoPath, "-")
+ _, err = w.Write(bundle)
+ require.NoError(t, err)
+ require.NoError(t, w.Close())
+
+ w, err = backupSink.GetWriter(ctx, step.RefPath)
+ require.NoError(t, err)
+ refs := gittest.Exec(t, cfg, "-C", templateRepoPath, "show-ref", "--head")
+ _, err = w.Write(refs)
+ require.NoError(t, err)
+ require.NoError(t, w.Close())
+
+ require.NoError(t, backupLocator.Commit(ctx, step))
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repo: repo,
+ repoPath: repoPath,
+ backupID: "abc123",
+ expectedChecksum: checksum,
+ }
+ },
+ },
+ {
+ desc: "missing backup ID",
+ setup: func(t *testing.T, ctx context.Context, backupSink backup.Sink, backupLocator backup.Locator) setupData {
+ cfg, client := setupRepositoryServiceWithoutRepo(t,
+ testserver.WithBackupSink(backupSink),
+ testserver.WithBackupLocator(backupLocator),
+ )
+
+ repo, repoPath := gittest.CreateRepository(t, ctx, cfg)
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repo: repo,
+ repoPath: repoPath,
+ backupID: "",
+ }
+ },
+ expectedErr: structerr.NewInvalidArgument("restore repository: empty BackupId"),
+ },
+ {
+ desc: "missing repository",
+ setup: func(t *testing.T, ctx context.Context, backupSink backup.Sink, backupLocator backup.Locator) setupData {
+ cfg, client := setupRepositoryServiceWithoutRepo(t,
+ testserver.WithBackupSink(backupSink),
+ testserver.WithBackupLocator(backupLocator),
+ )
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repo: nil,
+ backupID: "abc123",
+ }
+ },
+ expectedErr: structerr.NewInvalidArgument(testhelper.GitalyOrPraefect(
+ "restore repository: repository: repository not set",
+ "repository not set",
+ )),
+ },
+ {
+ desc: "missing backup sink",
+ setup: func(t *testing.T, ctx context.Context, backupSink backup.Sink, backupLocator backup.Locator) setupData {
+ cfg, client := setupRepositoryServiceWithoutRepo(t,
+ testserver.WithBackupLocator(backupLocator),
+ )
+
+ repo, repoPath := gittest.CreateRepository(t, ctx, cfg)
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repo: repo,
+ repoPath: repoPath,
+ backupID: "abc123",
+ }
+ },
+ expectedErr: structerr.NewFailedPrecondition("restore repository: server-side backups are not configured"),
+ },
+ {
+ desc: "missing backup locator",
+ setup: func(t *testing.T, ctx context.Context, backupSink backup.Sink, backupLocator backup.Locator) setupData {
+ cfg, client := setupRepositoryServiceWithoutRepo(t,
+ testserver.WithBackupSink(backupSink),
+ )
+
+ repo, repoPath := gittest.CreateRepository(t, ctx, cfg)
+
+ return setupData{
+ cfg: cfg,
+ client: client,
+ repo: repo,
+ repoPath: repoPath,
+ backupID: "abc123",
+ }
+ },
+ expectedErr: structerr.NewFailedPrecondition("restore repository: server-side backups are not configured"),
+ },
+ } {
+ tc := tc
+
+ t.Run(tc.desc, func(t *testing.T) {
+ t.Parallel()
+
+ backupRoot := testhelper.TempDir(t)
+ backupSink, err := backup.ResolveSink(ctx, backupRoot)
+ require.NoError(t, err)
+
+ backupLocator, err := backup.ResolveLocator("pointer", backupSink)
+ require.NoError(t, err)
+
+ data := tc.setup(t, ctx, backupSink, backupLocator)
+
+ response, err := data.client.RestoreRepository(ctx, &gitalypb.RestoreRepositoryRequest{
+ Repository: data.repo,
+ VanityRepository: data.repo,
+ BackupId: data.backupID,
+ })
+ if tc.expectedErr != nil {
+ testhelper.RequireGrpcError(t, tc.expectedErr, err)
+ return
+ }
+
+ require.NoError(t, err)
+ testhelper.ProtoEqual(t, &gitalypb.RestoreRepositoryResponse{}, response)
+
+ checksum := gittest.ChecksumRepo(t, data.cfg, data.repoPath)
+ require.Equal(t, data.expectedChecksum.String(), checksum.String())
+ })
+ }
+}