diff options
author | James Fargher <jfargher@gitlab.com> | 2023-06-26 03:02:35 +0300 |
---|---|---|
committer | James Fargher <jfargher@gitlab.com> | 2023-06-30 05:39:31 +0300 |
commit | f83c3549169c6637f7a59dc406c1580c3d32e836 (patch) | |
tree | 2ce2a2eaebc284fee5f9d5672109273223fa695d | |
parent | 536fcd1abf9ea505213496e52ae7fa6801e5776c (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.go | 4 | ||||
-rw-r--r-- | internal/gitaly/service/repository/restore_repository.go | 60 | ||||
-rw-r--r-- | internal/gitaly/service/repository/restore_repository_test.go | 190 |
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()) + }) + } +} |