package backup import ( "errors" "io/ioutil" "os" "path/filepath" "testing" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/gitaly/v14/internal/git/gittest" "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/service/setup" "gitlab.com/gitlab-org/gitaly/v14/internal/storage" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testcfg" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testserver" "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb" ) func TestFilesystem_Create(t *testing.T) { cfg := testcfg.Build(t) gitalyAddr := testserver.RunGitalyServer(t, cfg, nil, setup.RegisterAll) path := testhelper.TempDir(t) hooksRepo, hooksRepoPath, _ := gittest.CloneRepoAtStorage(t, cfg, cfg.Storages[0], "hooks") require.NoError(t, os.Mkdir(filepath.Join(hooksRepoPath, "custom_hooks"), os.ModePerm)) require.NoError(t, ioutil.WriteFile(filepath.Join(hooksRepoPath, "custom_hooks/pre-commit.sample"), []byte("Some hooks"), os.ModePerm)) noHooksRepo, _, _ := gittest.CloneRepoAtStorage(t, cfg, cfg.Storages[0], "no-hooks") emptyRepo, _, _ := gittest.InitBareRepoAt(t, cfg, cfg.Storages[0]) nonexistentRepo := *emptyRepo nonexistentRepo.RelativePath = "nonexistent" for _, tc := range []struct { desc string repo *gitalypb.Repository createsBundle bool createsCustomHooks bool err error }{ { desc: "no hooks", repo: noHooksRepo, createsBundle: true, createsCustomHooks: false, }, { desc: "hooks", repo: hooksRepo, createsBundle: true, createsCustomHooks: true, }, { desc: "empty repo", repo: emptyRepo, createsBundle: false, createsCustomHooks: false, err: ErrSkipped, }, { desc: "nonexistent repo", repo: &nonexistentRepo, createsBundle: false, createsCustomHooks: false, err: ErrSkipped, }, } { t.Run(tc.desc, func(t *testing.T) { repoPath := filepath.Join(cfg.Storages[0].Path, tc.repo.RelativePath) bundlePath := filepath.Join(path, tc.repo.RelativePath+".bundle") customHooksPath := filepath.Join(path, tc.repo.RelativePath, "custom_hooks.tar") ctx, cancel := testhelper.Context() defer cancel() fsBackup := NewFilesystem(path) err := fsBackup.Create(ctx, &CreateRequest{ Server: storage.ServerInfo{Address: gitalyAddr, Token: cfg.Auth.Token}, Repository: tc.repo, }) if tc.err == nil { require.NoError(t, err) } else { require.Equal(t, tc.err, err) } if tc.createsBundle { require.FileExists(t, bundlePath) dirInfo, err := os.Stat(filepath.Dir(bundlePath)) require.NoError(t, err) require.Equal(t, os.FileMode(0700), dirInfo.Mode().Perm(), "expecting restricted directory permissions") bundleInfo, err := os.Stat(bundlePath) require.NoError(t, err) require.Equal(t, os.FileMode(0600), bundleInfo.Mode().Perm(), "expecting restricted file permissions") output := gittest.Exec(t, cfg, "-C", repoPath, "bundle", "verify", bundlePath) require.Contains(t, string(output), "The bundle records a complete history") } else { require.NoFileExists(t, bundlePath) } if tc.createsCustomHooks { require.FileExists(t, customHooksPath) } else { require.NoFileExists(t, customHooksPath) } }) } } func TestFilesystem_Restore(t *testing.T) { cfg := testcfg.Build(t) testhelper.ConfigureGitalyHooksBin(t, cfg) gitalyAddr := testserver.RunGitalyServer(t, cfg, nil, setup.RegisterAll) path := testhelper.TempDir(t) existingRepo, existRepoPath, _ := gittest.CloneRepoAtStorage(t, cfg, cfg.Storages[0], "existing_repo") existingRepoPath := filepath.Join(path, existingRepo.RelativePath) existingRepoBundlePath := existingRepoPath + ".bundle" existingRepoCustomHooksPath := filepath.Join(existingRepoPath, "custom_hooks.tar") require.NoError(t, os.MkdirAll(existingRepoPath, os.ModePerm)) gittest.Exec(t, cfg, "-C", existRepoPath, "bundle", "create", existingRepoBundlePath, "--all") testhelper.CopyFile(t, "../gitaly/service/repository/testdata/custom_hooks.tar", existingRepoCustomHooksPath) newRepo := gittest.InitRepoDir(t, cfg.Storages[0].Path, "new_repo") newRepoBundlePath := filepath.Join(path, newRepo.RelativePath+".bundle") testhelper.CopyFile(t, existingRepoBundlePath, newRepoBundlePath) missingBundleRepo := gittest.InitRepoDir(t, cfg.Storages[0].Path, "missing_bundle") missingBundleRepoAlwaysCreate := gittest.InitRepoDir(t, cfg.Storages[0].Path, "missing_bundle_always_create") for _, tc := range []struct { desc string repo *gitalypb.Repository alwaysCreate bool expectedPaths []string expectedErrAs error expectVerify bool }{ { desc: "new repo, without hooks", repo: newRepo, expectVerify: true, }, { desc: "existing repo, with hooks", repo: existingRepo, expectedPaths: []string{ "custom_hooks/pre-commit.sample", "custom_hooks/prepare-commit-msg.sample", "custom_hooks/pre-push.sample", }, expectVerify: true, }, { desc: "missing bundle", repo: missingBundleRepo, expectedErrAs: ErrSkipped, }, { desc: "missing bundle, always create", repo: missingBundleRepoAlwaysCreate, alwaysCreate: true, }, } { t.Run(tc.desc, func(t *testing.T) { repoPath := filepath.Join(cfg.Storages[0].Path, tc.repo.RelativePath) bundlePath := filepath.Join(path, tc.repo.RelativePath+".bundle") ctx, cancel := testhelper.Context() defer cancel() fsBackup := NewFilesystem(path) err := fsBackup.Restore(ctx, &RestoreRequest{ Server: storage.ServerInfo{Address: gitalyAddr, Token: cfg.Auth.Token}, Repository: tc.repo, AlwaysCreate: tc.alwaysCreate, }) if tc.expectedErrAs != nil { require.True(t, errors.Is(err, tc.expectedErrAs), err.Error()) } else { require.NoError(t, err) } if tc.expectVerify { output := gittest.Exec(t, cfg, "-C", repoPath, "bundle", "verify", bundlePath) require.Contains(t, string(output), "The bundle records a complete history") } for _, p := range tc.expectedPaths { require.FileExists(t, filepath.Join(repoPath, p)) } }) } }