diff options
author | James Fargher <proglottis@gmail.com> | 2021-04-22 05:33:13 +0300 |
---|---|---|
committer | James Fargher <proglottis@gmail.com> | 2021-05-04 01:44:34 +0300 |
commit | 12aa02e6cf4ee69f4ab0f6af8948022e9e90f429 (patch) | |
tree | 688e6fbad86fb23f8eece4cac02f77d6da77a39b | |
parent | efaec4f88b7660801693759cbca43e80f35af605 (diff) |
gitaly-backup: Add restore command
-rw-r--r-- | cmd/gitaly-backup/main.go | 3 | ||||
-rw-r--r-- | cmd/gitaly-backup/restore.go | 74 | ||||
-rw-r--r-- | cmd/gitaly-backup/restore_test.go | 77 |
3 files changed, 153 insertions, 1 deletions
diff --git a/cmd/gitaly-backup/main.go b/cmd/gitaly-backup/main.go index 12cf21396..57173ca31 100644 --- a/cmd/gitaly-backup/main.go +++ b/cmd/gitaly-backup/main.go @@ -16,7 +16,8 @@ type subcmd interface { } var subcommands = map[string]subcmd{ - "create": &createSubcommand{}, + "create": &createSubcommand{}, + "restore": &restoreSubcommand{}, } func main() { diff --git a/cmd/gitaly-backup/restore.go b/cmd/gitaly-backup/restore.go new file mode 100644 index 000000000..619192a8a --- /dev/null +++ b/cmd/gitaly-backup/restore.go @@ -0,0 +1,74 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "flag" + "fmt" + "io" + + log "github.com/sirupsen/logrus" + "gitlab.com/gitlab-org/gitaly/internal/backup" + "gitlab.com/gitlab-org/gitaly/internal/storage" + "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" +) + +type restoreRequest struct { + storage.ServerInfo + StorageName string `json:"storage_name"` + RelativePath string `json:"relative_path"` + GlProjectPath string `json:"gl_project_path"` + AlwaysCreate bool `json:"always_create"` +} + +type restoreSubcommand struct { + backupPath string +} + +func (cmd *restoreSubcommand) Flags(fs *flag.FlagSet) { + fs.StringVar(&cmd.backupPath, "path", "", "repository backup path") +} + +func (cmd *restoreSubcommand) Run(ctx context.Context, stdin io.Reader, stdout io.Writer) error { + fsBackup := backup.NewFilesystem(cmd.backupPath) + + var failed int + decoder := json.NewDecoder(stdin) + for { + var req restoreRequest + if err := decoder.Decode(&req); errors.Is(err, io.EOF) { + break + } else if err != nil { + return fmt.Errorf("restore: %w", err) + } + + repoLog := log.WithFields(log.Fields{ + "storage_name": req.StorageName, + "relative_path": req.RelativePath, + "gl_project_path": req.GlProjectPath, + }) + repo := gitalypb.Repository{ + StorageName: req.StorageName, + RelativePath: req.RelativePath, + GlProjectPath: req.GlProjectPath, + } + repoLog.Info("started restore") + if err := fsBackup.RestoreRepository(ctx, req.ServerInfo, &repo, req.AlwaysCreate); err != nil { + if errors.Is(err, backup.ErrSkipped) { + repoLog.WithError(err).Warn("skipped restore") + } else { + repoLog.WithError(err).Error("restore failed") + failed++ + } + continue + } + + repoLog.Info("completed restore") + } + + if failed > 0 { + return fmt.Errorf("restore: %d failures encountered", failed) + } + return nil +} diff --git a/cmd/gitaly-backup/restore_test.go b/cmd/gitaly-backup/restore_test.go new file mode 100644 index 000000000..efccf131f --- /dev/null +++ b/cmd/gitaly-backup/restore_test.go @@ -0,0 +1,77 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/internal/git/gittest" + "gitlab.com/gitlab-org/gitaly/internal/gitaly/service/setup" + "gitlab.com/gitlab-org/gitaly/internal/testhelper" + "gitlab.com/gitlab-org/gitaly/internal/testhelper/testcfg" + "gitlab.com/gitlab-org/gitaly/internal/testhelper/testserver" + "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" +) + +func TestRestoreSubcommand(t *testing.T) { + cfg := testcfg.Build(t) + testhelper.ConfigureGitalyHooksBinary(cfg.BinDir) + + gitalyAddr := testserver.RunGitalyServer(t, cfg, nil, setup.RegisterAll) + + path := testhelper.TempDir(t) + + existingRepo, existRepoPath, _ := gittest.CloneRepoAtStorage(t, cfg.Storages[0], "existing_repo") + existingRepoBundlePath := filepath.Join(path, existingRepo.RelativePath+".bundle") + gittest.Exec(t, cfg, "-C", existRepoPath, "bundle", "create", existingRepoBundlePath, "--all") + + repos := []*gitalypb.Repository{existingRepo} + for i := 0; i < 2; i++ { + repo := gittest.InitRepoDir(t, cfg.Storages[0].Path, fmt.Sprintf("repo-%d", i)) + repoBundlePath := filepath.Join(path, repo.RelativePath+".bundle") + testhelper.CopyFile(t, existingRepoBundlePath, repoBundlePath) + repos = append(repos, repo) + } + + var stdin bytes.Buffer + + encoder := json.NewEncoder(&stdin) + for _, repo := range repos { + require.NoError(t, encoder.Encode(map[string]string{ + "address": gitalyAddr, + "token": cfg.Auth.Token, + "storage_name": repo.StorageName, + "relative_path": repo.RelativePath, + "gl_project_path": repo.GlProjectPath, + })) + } + + require.NoError(t, encoder.Encode(map[string]string{ + "address": "invalid", + "token": "invalid", + })) + + cmd := restoreSubcommand{} + + fs := flag.NewFlagSet("restore", flag.ContinueOnError) + cmd.Flags(fs) + + require.NoError(t, fs.Parse([]string{"-path", path})) + require.EqualError(t, + cmd.Run(context.Background(), &stdin, ioutil.Discard), + "restore: 1 failures encountered") + + for _, repo := range repos { + repoPath := filepath.Join(cfg.Storages[0].Path, repo.RelativePath) + bundlePath := filepath.Join(path, repo.RelativePath+".bundle") + + output := gittest.Exec(t, cfg, "-C", repoPath, "bundle", "verify", bundlePath) + require.Contains(t, string(output), "The bundle records a complete history") + } +} |