diff --git a/internal/git/catfile/cache_test.go b/internal/git/catfile/cache_test.go index db5ae2c67e4aa3bfa7dc864e7b5b83339588899c..797b145cbb1e150a123614cdfda70ce6a6aa22a9 100644 --- a/internal/git/catfile/cache_test.go +++ b/internal/git/catfile/cache_test.go @@ -433,8 +433,9 @@ func TestCache_ObjectReader_quarantine(t *testing.T) { }) gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("main")) - quarantineDir, err := quarantine.New(testhelper.Context(t), repo, logger, locator) + quarantineDir, cleanup, err := quarantine.New(testhelper.Context(t), repo, logger, locator) require.NoError(t, err) + defer func() { _ = cleanup() }() quarantineRepo := quarantineDir.QuarantinedRepo() diff --git a/internal/git/catfile/object_reader_test.go b/internal/git/catfile/object_reader_test.go index f5fee1bbed0dec50540f24cec4c43791596b0a5a..1df7dbdba16b962949d2f6b22414081a99e953ab 100644 --- a/internal/git/catfile/object_reader_test.go +++ b/internal/git/catfile/object_reader_test.go @@ -720,8 +720,9 @@ func TestObjectReader_logging(t *testing.T) { SkipCreationViaService: true, }) - quarantineDir, err := quarantine.New(ctx, repoProto, logger, locator) + quarantineDir, cleanup, err := quarantine.New(ctx, repoProto, logger, locator) require.NoError(t, err) + defer func() { _ = cleanup() }() reader, err := newObjectReader(ctx, newRepoExecutor(t, cfg, quarantineDir.QuarantinedRepo()), nil) require.NoError(t, err) diff --git a/internal/git/localrepo/bundle.go b/internal/git/localrepo/bundle.go index d033174e4c531457a4dcb5edc2087cc6096310ba..0e091fa9d0396fa77bb0a9298eca6980372d103a 100644 --- a/internal/git/localrepo/bundle.go +++ b/internal/git/localrepo/bundle.go @@ -77,10 +77,15 @@ func (repo *Repo) CreateBundle(ctx context.Context, out io.Writer, opts *CreateB func (repo *Repo) CloneBundle(ctx context.Context, reader io.Reader) error { // When cloning from a file, `git-clone(1)` requires the path to the file. Create a temporary // file with the Git bundle contents that is used for cloning. - bundlePath, err := repo.createTempBundle(ctx, reader) + bundlePath, cleanup, err := repo.createTempBundle(ctx, reader) if err != nil { return err } + defer func() { + if err := cleanup(); err != nil { + repo.logger.WithError(err).Error("failed to cleanup temp bundle directory") + } + }() repoPath, err := repo.locator.GetRepoPath(ctx, repo, storage.WithRepositoryVerificationSkipped()) if err != nil { @@ -156,10 +161,15 @@ func (repo *Repo) FetchBundle(ctx context.Context, txManager transaction.Manager opts = &FetchBundleOpts{} } - bundlePath, err := repo.createTempBundle(ctx, reader) + bundlePath, cleanup, err := repo.createTempBundle(ctx, reader) if err != nil { return fmt.Errorf("fetch bundle: %w", err) } + defer func() { + if err := cleanup(); err != nil { + repo.logger.WithError(err).Error("failed to cleanup temp bundle directory") + } + }() fetchConfig := []gitcmd.ConfigPair{ {Key: "remote.inmemory.url", Value: bundlePath}, @@ -192,17 +202,19 @@ func (repo *Repo) FetchBundle(ctx context.Context, txManager transaction.Manager // createTempBundle copies reader onto the filesystem so that a path can be // passed to git. git-fetch does not support streaming a bundle over a pipe. -func (repo *Repo) createTempBundle(ctx context.Context, reader io.Reader) (bundlPath string, returnErr error) { - tmpDir, err := tempdir.New(ctx, repo.GetStorageName(), repo.logger, repo.locator) +// The caller is responsible for calling the returned cleanup function. +func (repo *Repo) createTempBundle(ctx context.Context, reader io.Reader) (bundlPath string, cleanup func() error, returnErr error) { + tmpDir, cleanup, err := tempdir.New(ctx, repo.GetStorageName(), repo.logger, repo.locator) if err != nil { - return "", fmt.Errorf("create temp bundle: %w", err) + return "", nil, fmt.Errorf("create temp bundle: %w", err) } bundlePath := filepath.Join(tmpDir.Path(), "repo.bundle") file, err := os.Create(bundlePath) if err != nil { - return "", fmt.Errorf("create temp bundle: %w", err) + _ = cleanup() // Clean up if we fail after creating the temp directory + return "", nil, fmt.Errorf("create temp bundle: %w", err) } defer func() { if err := file.Close(); err != nil && returnErr == nil { @@ -211,10 +223,11 @@ func (repo *Repo) createTempBundle(ctx context.Context, reader io.Reader) (bundl }() if _, err = io.Copy(file, reader); err != nil { - return "", fmt.Errorf("create temp bundle: %w", err) + _ = cleanup() // Clean up if we fail after creating the temp directory + return "", nil, fmt.Errorf("create temp bundle: %w", err) } - return bundlePath, nil + return bundlePath, cleanup, nil } // updateHeadFromBundle updates HEAD from a bundle file diff --git a/internal/git/localrepo/paths_test.go b/internal/git/localrepo/paths_test.go index 6d737639a4d6549e709af2cdce549b38f3f6bf71..71cf74de66db3e92ab67ff8d15676b92d72f3f49 100644 --- a/internal/git/localrepo/paths_test.go +++ b/internal/git/localrepo/paths_test.go @@ -73,8 +73,9 @@ func TestRepo_ObjectDirectoryPath(t *testing.T) { }) locator := config.NewLocator(cfg) - quarantine, err := quarantine.New(ctx, repoProto, testhelper.NewLogger(t), locator) + quarantine, cleanup, err := quarantine.New(ctx, repoProto, testhelper.NewLogger(t), locator) require.NoError(t, err) + defer func() { _ = cleanup() }() quarantinedRepo := quarantine.QuarantinedRepo() // Transactions store their set a quarantine directory in the transaction's temporary diff --git a/internal/git/quarantine/quarantine.go b/internal/git/quarantine/quarantine.go index e04849241a43f5c6afc5460b525288d33e7990f4..1bc5dbe3c977ed45d11f426d6c623db3d4368f70 100644 --- a/internal/git/quarantine/quarantine.go +++ b/internal/git/quarantine/quarantine.go @@ -32,23 +32,24 @@ type Dir struct { locator storage.Locator } -// New creates a new quarantine directory and returns the directory. The repository is cleaned -// up when the user invokes the Migrate() functionality on the Dir. -func New(ctx context.Context, repo *gitalypb.Repository, logger log.Logger, locator storage.Locator) (*Dir, error) { +// New creates a new quarantine directory and returns the directory and a cleanup function. +// The cleanup function must be called to remove the quarantine directory. +func New(ctx context.Context, repo *gitalypb.Repository, logger log.Logger, locator storage.Locator) (*Dir, func() error, error) { repoPath, err := locator.GetRepoPath(ctx, repo, storage.WithRepositoryVerificationSkipped()) if err != nil { - return nil, structerr.NewInternal("getting repo path: %w", err) + return nil, nil, structerr.NewInternal("getting repo path: %w", err) } - quarantineDir, err := tempdir.NewWithPrefix(ctx, repo.GetStorageName(), + quarantineDir, cleanup, err := tempdir.NewWithPrefix(ctx, repo.GetStorageName(), storage.QuarantineDirectoryPrefix(repo), logger, locator) if err != nil { - return nil, fmt.Errorf("creating quarantine: %w", err) + return nil, nil, fmt.Errorf("creating quarantine: %w", err) } quarantinedRepo, err := Apply(repoPath, repo, quarantineDir.Path()) if err != nil { - return nil, err + _ = cleanup() // Clean up if we fail after creating the temp directory + return nil, nil, err } return &Dir{ @@ -56,7 +57,7 @@ func New(ctx context.Context, repo *gitalypb.Repository, logger log.Logger, loca quarantinedRepo: quarantinedRepo, locator: locator, dir: quarantineDir, - }, nil + }, cleanup, nil } // Apply applies the quarantine on the repository. This is done by setting the quarantineDirectory diff --git a/internal/git/quarantine/quarantine_ext_test.go b/internal/git/quarantine/quarantine_ext_test.go index 97fc28f851fa388c015595ed1e16ddbe4e0ca04a..89d5ece80253e23c50d9cdddf8fcc112ff573f7f 100644 --- a/internal/git/quarantine/quarantine_ext_test.go +++ b/internal/git/quarantine/quarantine_ext_test.go @@ -28,8 +28,9 @@ func TestQuarantine_localrepo(t *testing.T) { locator := config.NewLocator(cfg) - quarantine, err := quarantine.New(ctx, repoProto, testhelper.NewLogger(t), locator) + quarantine, cleanup, err := quarantine.New(ctx, repoProto, testhelper.NewLogger(t), locator) require.NoError(t, err) + defer func() { _ = cleanup() }() quarantined := localrepo.NewTestRepo(t, cfg, quarantine.QuarantinedRepo()) diff --git a/internal/git/quarantine/quarantine_test.go b/internal/git/quarantine/quarantine_test.go index 208af699e9bef02ff10fc501ab4f12ee00cfe01f..d69a42af53f127fd41ed108473bd56ad007ecdb4 100644 --- a/internal/git/quarantine/quarantine_test.go +++ b/internal/git/quarantine/quarantine_test.go @@ -1,7 +1,6 @@ package quarantine import ( - "context" "os" "path/filepath" "testing" @@ -50,8 +49,9 @@ func TestQuarantine_lifecycle(t *testing.T) { logger := testhelper.NewLogger(t) t.Run("quarantine directory gets created", func(t *testing.T) { - quarantine, err := New(ctx, repo, logger, locator) + quarantine, cleanup, err := New(ctx, repo, logger, locator) require.NoError(t, err) + defer func() { _ = cleanup() }() relativeQuarantinePath, err := filepath.Rel(repoPath, quarantine.dir.Path()) require.NoError(t, err) @@ -72,15 +72,12 @@ func TestQuarantine_lifecycle(t *testing.T) { require.DirExists(t, quarantine.dir.Path()) }) - t.Run("context cancellation cleans up quarantine directory", func(t *testing.T) { - ctx, cancel := context.WithCancel(ctx) - - quarantine, err := New(ctx, repo, logger, locator) + t.Run("explicit cleanup removes quarantine directory", func(t *testing.T) { + quarantine, cleanup, err := New(ctx, repo, logger, locator) require.NoError(t, err) require.DirExists(t, quarantine.dir.Path()) - cancel() - quarantine.dir.WaitForCleanup() + require.NoError(t, cleanup()) require.NoDirExists(t, quarantine.dir.Path()) }) } @@ -102,8 +99,9 @@ func TestQuarantine_Migrate(t *testing.T) { oldContents := listEntries(t, repoPath) - quarantine, err := New(ctx, repo, logger, locator) + quarantine, cleanup, err := New(ctx, repo, logger, locator) require.NoError(t, err) + defer func() { _ = cleanup() }() require.NoError(t, quarantine.Migrate(ctx)) @@ -120,8 +118,9 @@ func TestQuarantine_Migrate(t *testing.T) { oldContents := listEntries(t, repoPath) require.NotContains(t, oldContents, "objects/file") - quarantine, err := New(ctx, repo, logger, locator) + quarantine, cleanup, err := New(ctx, repo, logger, locator) require.NoError(t, err) + defer func() { _ = cleanup() }() require.NoError(t, os.WriteFile(filepath.Join(quarantine.dir.Path(), "file"), []byte("foobar"), mode.File)) require.NoError(t, quarantine.Migrate(ctx)) @@ -143,16 +142,18 @@ func TestQuarantine_Migrate(t *testing.T) { repoContents := listEntries(t, repoPath) require.NotContains(t, repoContents, "objects/file") - quarantine, err := New(ctx, repo, logger, locator) + quarantine, cleanup, err := New(ctx, repo, logger, locator) require.NoError(t, err) + defer func() { _ = cleanup() }() require.Empty(t, listEntries(t, quarantine.dir.Path())) // Quarantine the already quarantined repository and write the object there. We expect the // object to be migrated from the second level quarantine to the first level quarantine. The // main repository should stay untouched. - recursiveQuarantine, err := New(ctx, quarantine.QuarantinedRepo(), logger, locator) + recursiveQuarantine, recursiveCleanup, err := New(ctx, quarantine.QuarantinedRepo(), logger, locator) require.NoError(t, err) + defer func() { _ = recursiveCleanup() }() require.NoError(t, os.WriteFile(filepath.Join(recursiveQuarantine.dir.Path(), "file"), []byte("foobar"), mode.File)) require.NoError(t, recursiveQuarantine.Migrate(ctx)) diff --git a/internal/gitaly/hook/postreceive_test.go b/internal/gitaly/hook/postreceive_test.go index f6cd89b992b32d0f9f4df5b90ffb0e62e83b9e56..09e01b26e4804c1780f7b15db4c5b1d6472f5d6d 100644 --- a/internal/gitaly/hook/postreceive_test.go +++ b/internal/gitaly/hook/postreceive_test.go @@ -427,8 +427,9 @@ func TestPostReceive_quarantine(t *testing.T) { SkipCreationViaService: true, }) - quarantine, err := quarantine.New(ctx, repoProto, testhelper.SharedLogger(t), config.NewLocator(cfg)) + quarantine, cleanup, err := quarantine.New(ctx, repoProto, testhelper.SharedLogger(t), config.NewLocator(cfg)) require.NoError(t, err) + defer func() { _ = cleanup() }() quarantinedRepo := localrepo.NewTestRepo(t, cfg, quarantine.QuarantinedRepo()) blobID, err := quarantinedRepo.WriteBlob(ctx, strings.NewReader("allyourbasearebelongtous"), localrepo.WriteBlobConfig{}) diff --git a/internal/gitaly/hook/prereceive_test.go b/internal/gitaly/hook/prereceive_test.go index 66e407607f4a82376743c7e1ea9d4b38370d7286..64c4b2769d27d7eb2f89573cb41d5461cf5c13dd 100644 --- a/internal/gitaly/hook/prereceive_test.go +++ b/internal/gitaly/hook/prereceive_test.go @@ -223,8 +223,9 @@ func TestPrereceive_quarantine(t *testing.T) { SkipCreationViaService: true, }) - quarantine, err := quarantine.New(ctx, repoProto, testhelper.SharedLogger(t), config.NewLocator(cfg)) + quarantine, cleanup, err := quarantine.New(ctx, repoProto, testhelper.SharedLogger(t), config.NewLocator(cfg)) require.NoError(t, err) + defer func() { _ = cleanup() }() quarantinedRepo := localrepo.NewTestRepo(t, cfg, quarantine.QuarantinedRepo()) blobID, err := quarantinedRepo.WriteBlob(ctx, strings.NewReader("allyourbasearebelongtous"), localrepo.WriteBlobConfig{}) diff --git a/internal/gitaly/hook/update_test.go b/internal/gitaly/hook/update_test.go index 4287d271683e19a16a236ac2939fae2e4dd0a0d7..a39f78f17251dc3c0d83e6160f02798a22f50938 100644 --- a/internal/gitaly/hook/update_test.go +++ b/internal/gitaly/hook/update_test.go @@ -253,8 +253,9 @@ func TestUpdate_quarantine(t *testing.T) { SkipCreationViaService: true, }) - quarantine, err := quarantine.New(ctx, repoProto, testhelper.SharedLogger(t), config.NewLocator(cfg)) + quarantine, cleanup, err := quarantine.New(ctx, repoProto, testhelper.SharedLogger(t), config.NewLocator(cfg)) require.NoError(t, err) + defer func() { _ = cleanup() }() quarantinedRepo := localrepo.NewTestRepo(t, cfg, quarantine.QuarantinedRepo()) blobID, err := quarantinedRepo.WriteBlob(ctx, strings.NewReader("allyourbasearebelongtous"), localrepo.WriteBlobConfig{}) diff --git a/internal/gitaly/hook/updateref/update_with_hooks_test.go b/internal/gitaly/hook/updateref/update_with_hooks_test.go index 72b743c672b42ce34208a486c90e32938decd2a1..edd6d561984b626f4e7b14b9a49190df122f0c43 100644 --- a/internal/gitaly/hook/updateref/update_with_hooks_test.go +++ b/internal/gitaly/hook/updateref/update_with_hooks_test.go @@ -318,8 +318,9 @@ func TestUpdaterWithHooks_quarantine(t *testing.T) { unquarantinedRepo := localrepo.NewTestRepo(t, cfg, repoProto) - quarantine, err := quarantine.New(ctx, repoProto, testhelper.NewLogger(t), locator) + quarantine, cleanup, err := quarantine.New(ctx, repoProto, testhelper.NewLogger(t), locator) require.NoError(t, err) + defer func() { _ = cleanup() }() quarantinedRepo := localrepo.NewTestRepo(t, cfg, quarantine.QuarantinedRepo()) blobID, err := quarantinedRepo.WriteBlob(ctx, strings.NewReader("1834298812398123"), localrepo.WriteBlobConfig{}) require.NoError(t, err) diff --git a/internal/gitaly/repoutil/create.go b/internal/gitaly/repoutil/create.go index f849cdb8ed05c60351c18b546e2e03cde4a9eaa6..d85c97583fcfd8e3f646ddf73b18b0e14fe8758a 100644 --- a/internal/gitaly/repoutil/create.go +++ b/internal/gitaly/repoutil/create.go @@ -104,15 +104,13 @@ func Create( return fmt.Errorf("pre-lock stat: %w", err) } - newRepoProto, newRepoDir, err := tempdir.NewRepository(ctx, repository.GetStorageName(), logger, locator) + newRepoProto, newRepoDir, cleanup, err := tempdir.NewRepository(ctx, repository.GetStorageName(), logger, locator) if err != nil { return fmt.Errorf("creating temporary repository: %w", err) } defer func() { - // We don't really care about whether this succeeds or not. It will either get - // cleaned up after the context is done, or eventually by the tempdir walker when - // it's old enough. - _ = os.RemoveAll(newRepoDir.Path()) + // We don't really care about whether this succeeds or not. + _ = cleanup() }() // Note that we do not create the repository directly in its target location, but diff --git a/internal/gitaly/service/blob/blobs_test.go b/internal/gitaly/service/blob/blobs_test.go index 690539c0b5bb31fb99c5b07b2997cc8d797cd7f6..1de2d551de8df8a85f479cbcc20e2c038c6ce0ce 100644 --- a/internal/gitaly/service/blob/blobs_test.go +++ b/internal/gitaly/service/blob/blobs_test.go @@ -331,8 +331,9 @@ func TestListAllBlobs(t *testing.T) { repo, _, _ := setupRepoWithLFS(t, ctx, cfg) - quarantine, err := quarantine.New(ctx, gittest.RewrittenRepository(t, ctx, cfg, repo), testhelper.NewLogger(t), config.NewLocator(cfg)) + quarantine, cleanup, err := quarantine.New(ctx, gittest.RewrittenRepository(t, ctx, cfg, repo), testhelper.NewLogger(t), config.NewLocator(cfg)) require.NoError(t, err) + t.Cleanup(func() { _ = cleanup() }) // quarantine.New in Gitaly would receive an already rewritten repository. Gitaly would then calculate // the quarantine directories based on the rewritten relative path. That quarantine would then be looped diff --git a/internal/gitaly/service/blob/lfs_pointers_test.go b/internal/gitaly/service/blob/lfs_pointers_test.go index b4dffb415d53b806ac0c34bc654b9de790043a92..54925ac464cc30cd2ec7522301988c4aa3700223 100644 --- a/internal/gitaly/service/blob/lfs_pointers_test.go +++ b/internal/gitaly/service/blob/lfs_pointers_test.go @@ -218,8 +218,9 @@ size 12345` setup: func(t *testing.T) setupData { repo, _, _ := setupRepoWithLFS(t, ctx, cfg) - quarantineDir, err := quarantine.New(ctx, gittest.RewrittenRepository(t, ctx, cfg, repo), testhelper.NewLogger(t), config.NewLocator(cfg)) + quarantineDir, cleanup, err := quarantine.New(ctx, gittest.RewrittenRepository(t, ctx, cfg, repo), testhelper.NewLogger(t), config.NewLocator(cfg)) require.NoError(t, err) + t.Cleanup(func() { _ = cleanup() }) repo.GitObjectDirectory = quarantineDir.QuarantinedRepo().GetGitObjectDirectory() @@ -241,8 +242,9 @@ size 12345` // this case, LFS pointer checks may want to inspect all newly // pushed objects, denoted by a repository proto message which only // has its object directory set to the quarantine directory. - quarantineDir, err := quarantine.New(ctx, gittest.RewrittenRepository(t, ctx, cfg, repo), testhelper.NewLogger(t), config.NewLocator(cfg)) + quarantineDir, cleanup, err := quarantine.New(ctx, gittest.RewrittenRepository(t, ctx, cfg, repo), testhelper.NewLogger(t), config.NewLocator(cfg)) require.NoError(t, err) + t.Cleanup(func() { _ = cleanup() }) // Note that we need to continue using the non-rewritten repository // here as `localrepo.NewTestRepo()` already will try to rewrite it diff --git a/internal/gitaly/service/cleanup/rewrite_history.go b/internal/gitaly/service/cleanup/rewrite_history.go index 16bf818cfef52b33f10eba18fcffabceb381ca39..8866b49a9d0d8d4c2ad0418129dde8f4dd5f6471 100644 --- a/internal/gitaly/service/cleanup/rewrite_history.go +++ b/internal/gitaly/service/cleanup/rewrite_history.go @@ -110,10 +110,15 @@ func (s *server) rewriteHistory( return fmt.Errorf("finding HEAD reference: %w", err) } - stagingRepo, stagingRepoPath, err := s.initStagingRepo(ctx, repoProto, defaultBranch) + stagingRepo, stagingRepoPath, cleanup, err := s.initStagingRepo(ctx, repoProto, defaultBranch) if err != nil { return fmt.Errorf("setting up staging repo: %w", err) } + defer func() { + if err := cleanup(); err != nil { + s.logger.WithError(err).Error("failed to cleanup staging repo directory") + } + }() // Check state of source repository prior to running filter-repo. initialChecksum, err := checksumRepo(ctx, repo) @@ -185,11 +190,11 @@ func (s *server) rewriteHistory( } // initStagingRepo creates a new bare repository to write the rewritten history into -// with default branch is set to match the source repo. -func (s *server) initStagingRepo(ctx context.Context, repo *gitalypb.Repository, defaultBranch git.ReferenceName) (*localrepo.Repo, string, error) { - stagingRepoProto, stagingRepoDir, err := tempdir.NewRepository(ctx, repo.GetStorageName(), s.logger, s.locator) +// with default branch is set to match the source repo. Returns the repo, path, and cleanup function. +func (s *server) initStagingRepo(ctx context.Context, repo *gitalypb.Repository, defaultBranch git.ReferenceName) (*localrepo.Repo, string, func() error, error) { + stagingRepoProto, stagingRepoDir, cleanup, err := tempdir.NewRepository(ctx, repo.GetStorageName(), s.logger, s.locator) if err != nil { - return nil, "", err + return nil, "", nil, err } var stderr strings.Builder @@ -202,11 +207,13 @@ func (s *server) initStagingRepo(ctx context.Context, repo *gitalypb.Repository, Args: []string{stagingRepoDir.Path()}, }, gitcmd.WithStderr(&stderr)) if err != nil { - return nil, "", fmt.Errorf("spawning git-init: %w", err) + _ = cleanup() // Clean up on error + return nil, "", nil, fmt.Errorf("spawning git-init: %w", err) } if err := cmd.Wait(); err != nil { - return nil, "", structerr.New("creating repository: %w", err).WithMetadata("stderr", &stderr) + _ = cleanup() // Clean up on error + return nil, "", nil, structerr.New("creating repository: %w", err).WithMetadata("stderr", &stderr) } stagingRepo := s.localRepoFactory.Build(stagingRepoProto) @@ -214,10 +221,11 @@ func (s *server) initStagingRepo(ctx context.Context, repo *gitalypb.Repository, // Ensure HEAD matches the source repository. In practice a mismatch doesn't cause problems, // but out of an abundance of caution let's keep the two repos as similar as possible. if err := stagingRepo.SetDefaultBranch(ctx, s.txManager, defaultBranch); err != nil { - return nil, "", fmt.Errorf("setting default branch: %w", err) + _ = cleanup() // Clean up on error + return nil, "", nil, fmt.Errorf("setting default branch: %w", err) } - return stagingRepo, stagingRepoDir.Path(), nil + return stagingRepo, stagingRepoDir.Path(), cleanup, nil } func (s *server) runFilterRepo( @@ -227,10 +235,15 @@ func (s *server) runFilterRepo( redactions [][]byte, ) error { // Place argument files in a tempdir so that cleanup is handled automatically. - tmpDir, err := tempdir.New(ctx, srcRepo.GetStorageName(), s.logger, s.locator) + tmpDir, cleanup, err := tempdir.New(ctx, srcRepo.GetStorageName(), s.logger, s.locator) if err != nil { return fmt.Errorf("create tempdir: %w", err) } + defer func() { + if err := cleanup(); err != nil { + s.logger.WithError(err).Error("failed to cleanup temp directory") + } + }() flags := make([]gitcmd.Option, 0, 2) diff --git a/internal/gitaly/service/conflicts/list_conflict_files.go b/internal/gitaly/service/conflicts/list_conflict_files.go index 0bb926fe52dc3364c4318afc72f8efbe42db969c..61177f7dedbc21c89f70a66cea456a73256a6f15 100644 --- a/internal/gitaly/service/conflicts/list_conflict_files.go +++ b/internal/gitaly/service/conflicts/list_conflict_files.go @@ -8,6 +8,7 @@ import ( "io" "unicode/utf8" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage" @@ -23,10 +24,15 @@ func (s *server) ListConflictFiles(request *gitalypb.ListConflictFilesRequest, s return structerr.NewInvalidArgument("%w", err) } - _, quarantineRepo, err := s.quarantinedRepo(ctx, request.GetRepository()) + _, quarantineRepo, cleanup, err := s.quarantinedRepo(ctx, request.GetRepository()) if err != nil { return err } + defer func() { + if err := cleanup(); err != nil { + ctxlogrus.Extract(ctx).WithError(err).Error("failed to cleanup quarantine directory") + } + }() ours, err := quarantineRepo.ResolveRevision(ctx, git.Revision(request.GetOurCommitOid()+"^{commit}")) if err != nil { diff --git a/internal/gitaly/service/conflicts/resolve_conflicts.go b/internal/gitaly/service/conflicts/resolve_conflicts.go index c902e22813604c270f1329c5ba1a6be09f5e4d49..858f2e8ab8a013947201c94e303f055b77cf2f1a 100644 --- a/internal/gitaly/service/conflicts/resolve_conflicts.go +++ b/internal/gitaly/service/conflicts/resolve_conflicts.go @@ -11,6 +11,7 @@ import ( "sort" "strings" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/git/conflict" "gitlab.com/gitlab-org/gitaly/v16/internal/git/localrepo" @@ -143,10 +144,15 @@ func (s *server) resolveConflicts(header *gitalypb.ResolveConflictsRequestHeader return err } - quarantineDir, quarantineRepo, err := s.quarantinedRepo(ctx, header.GetRepository()) + quarantineDir, quarantineRepo, cleanup, err := s.quarantinedRepo(ctx, header.GetRepository()) if err != nil { return err } + defer func() { + if err := cleanup(); err != nil { + ctxlogrus.Extract(ctx).WithError(err).Error("failed to cleanup quarantine directory") + } + }() if err := s.repoWithBranchCommit(ctx, quarantineRepo, diff --git a/internal/gitaly/service/conflicts/server.go b/internal/gitaly/service/conflicts/server.go index a1178a071a175c2516798320cefb8ecc5157af52..74c1602ac2b92d43d9d59519d930a4f00c4e8ea8 100644 --- a/internal/gitaly/service/conflicts/server.go +++ b/internal/gitaly/service/conflicts/server.go @@ -40,12 +40,12 @@ func NewServer(deps *service.Dependencies) gitalypb.ConflictsServiceServer { } } -func (s *server) quarantinedRepo(ctx context.Context, repo *gitalypb.Repository) (*quarantine.Dir, *localrepo.Repo, error) { - quarantineDir, err := quarantine.New(ctx, repo, s.logger, s.locator) +func (s *server) quarantinedRepo(ctx context.Context, repo *gitalypb.Repository) (*quarantine.Dir, *localrepo.Repo, func() error, error) { + quarantineDir, cleanup, err := quarantine.New(ctx, repo, s.logger, s.locator) if err != nil { - return nil, nil, structerr.NewInternal("creating object quarantine: %w", err) + return nil, nil, nil, structerr.NewInternal("creating object quarantine: %w", err) } quarantineRepo := s.localRepoFactory.Build(quarantineDir.QuarantinedRepo()) - return quarantineDir, quarantineRepo, nil + return quarantineDir, quarantineRepo, cleanup, nil } diff --git a/internal/gitaly/service/diff/diff_blobs.go b/internal/gitaly/service/diff/diff_blobs.go index 9b4e31cc97cabd35171e1f9dd583c646717d3d40..1897b7def3e55fbf62897ee6889e838f46644eea 100644 --- a/internal/gitaly/service/diff/diff_blobs.go +++ b/internal/gitaly/service/diff/diff_blobs.go @@ -7,6 +7,7 @@ import ( "fmt" "strings" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/git/catfile" "gitlab.com/gitlab-org/gitaly/v16/internal/git/gitcmd" @@ -148,10 +149,15 @@ func (s *server) diffBlobs(ctx context.Context, // of right blob pair. Unlike an empty tree object, an empty blob object is not special cased // and must exist in the repository to be used. Since the DiffBlobs RPC is read-only, we create // a quarantine directory to stage an empty blob object for use with diff generation only. - quarantineDir, err := quarantine.New(ctx, request.GetRepository(), s.logger, s.locator) + quarantineDir, cleanup, err := quarantine.New(ctx, request.GetRepository(), s.logger, s.locator) if err != nil { return structerr.NewInternal("creating quarantine directory: %w", err) } + defer func() { + if err := cleanup(); err != nil { + ctxlogrus.Extract(ctx).WithError(err).Error("failed to cleanup quarantine directory") + } + }() repo := s.localRepoFactory.Build(quarantineDir.QuarantinedRepo()) diff --git a/internal/gitaly/service/operations/cherry_pick.go b/internal/gitaly/service/operations/cherry_pick.go index dbd12c8116b1b4b05fb7e525a82686e40f8f722e..46ec13c3afb8f67a1dca6633170862fe0a1b147b 100644 --- a/internal/gitaly/service/operations/cherry_pick.go +++ b/internal/gitaly/service/operations/cherry_pick.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/hook/updateref" @@ -21,10 +22,15 @@ func (s *Server) UserCherryPick(ctx context.Context, req *gitalypb.UserCherryPic return nil, structerr.NewInvalidArgument("%w", err) } - quarantineDir, quarantineRepo, err := s.quarantinedRepo(ctx, req.GetRepository()) + quarantineDir, quarantineRepo, cleanup, err := s.quarantinedRepo(ctx, req.GetRepository()) if err != nil { return nil, err } + defer func() { + if err := cleanup(); err != nil { + ctxlogrus.Extract(ctx).WithError(err).Error("failed to cleanup quarantine directory") + } + }() startRevision, err := s.fetchStartRevision(ctx, quarantineRepo, req) if err != nil { diff --git a/internal/gitaly/service/operations/commit_files.go b/internal/gitaly/service/operations/commit_files.go index 5913288f05d20bab5012df8a3f03061424c95537..a73293ef83906568ff1cd24623617f009e55b801 100644 --- a/internal/gitaly/service/operations/commit_files.go +++ b/internal/gitaly/service/operations/commit_files.go @@ -10,6 +10,7 @@ import ( "path/filepath" "strings" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/git/gitcmd" "gitlab.com/gitlab-org/gitaly/v16/internal/git/localrepo" @@ -521,10 +522,15 @@ func (s *Server) userCommitFiles( stream gitalypb.OperationService_UserCommitFilesServer, objectHash git.ObjectHash, ) error { - quarantineDir, quarantineRepo, err := s.quarantinedRepo(ctx, header.GetRepository()) + quarantineDir, quarantineRepo, cleanup, err := s.quarantinedRepo(ctx, header.GetRepository()) if err != nil { return err } + defer func() { + if err := cleanup(); err != nil { + ctxlogrus.Extract(ctx).WithError(err).Error("failed to cleanup quarantine directory") + } + }() repoPath, err := quarantineRepo.Path(ctx) if err != nil { diff --git a/internal/gitaly/service/operations/ff_branch.go b/internal/gitaly/service/operations/ff_branch.go index e78c8c5e00a27ebf296d445d95a1b54741b5b372..d4aee6e90e29336c4325abaa69479e51bc54ba7c 100644 --- a/internal/gitaly/service/operations/ff_branch.go +++ b/internal/gitaly/service/operations/ff_branch.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/hook/updateref" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage" @@ -23,10 +24,15 @@ func (s *Server) UserFFBranch(ctx context.Context, in *gitalypb.UserFFBranchRequ // While we're creating a quarantine directory, we know that it won't ever have any new // objects given that we're doing a fast-forward merge. We still want to create one such // that Rails can efficiently compute new objects. - quarantineDir, quarantineRepo, err := s.quarantinedRepo(ctx, in.GetRepository()) + quarantineDir, quarantineRepo, cleanup, err := s.quarantinedRepo(ctx, in.GetRepository()) if err != nil { return nil, err } + defer func() { + if err := cleanup(); err != nil { + ctxlogrus.Extract(ctx).WithError(err).Error("failed to cleanup quarantine directory") + } + }() objectHash, err := quarantineRepo.ObjectHash(ctx) if err != nil { diff --git a/internal/gitaly/service/operations/merge_branch.go b/internal/gitaly/service/operations/merge_branch.go index d8066d1257d753923f147a463bcf5af7fb547ef4..725b209f3d5e3eb5fd3ffb14ccf9960fa54a4f87 100644 --- a/internal/gitaly/service/operations/merge_branch.go +++ b/internal/gitaly/service/operations/merge_branch.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/hook" @@ -29,10 +30,15 @@ func (s *Server) UserMergeBranch(stream gitalypb.OperationService_UserMergeBranc return structerr.NewInvalidArgument("%w", err) } - quarantineDir, quarantineRepo, err := s.quarantinedRepo(ctx, firstRequest.GetRepository()) + quarantineDir, quarantineRepo, cleanup, err := s.quarantinedRepo(ctx, firstRequest.GetRepository()) if err != nil { return err } + defer func() { + if err := cleanup(); err != nil { + ctxlogrus.Extract(ctx).WithError(err).Error("failed to cleanup quarantine directory") + } + }() objectHash, err := quarantineRepo.ObjectHash(ctx) if err != nil { diff --git a/internal/gitaly/service/operations/rebase_confirmable.go b/internal/gitaly/service/operations/rebase_confirmable.go index 0f4a1d0301051457ccb39b3630e4cc31a0a58cb9..5cb3ee716a8d672842584240f7eba574eacf0e14 100644 --- a/internal/gitaly/service/operations/rebase_confirmable.go +++ b/internal/gitaly/service/operations/rebase_confirmable.go @@ -6,6 +6,7 @@ import ( "fmt" "strings" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/hook/updateref" @@ -31,10 +32,15 @@ func (s *Server) UserRebaseConfirmable(stream gitalypb.OperationService_UserReba return structerr.NewInvalidArgument("%w", err) } - quarantineDir, quarantineRepo, err := s.quarantinedRepo(ctx, header.GetRepository()) + quarantineDir, quarantineRepo, cleanup, err := s.quarantinedRepo(ctx, header.GetRepository()) if err != nil { return structerr.NewInternal("creating repo quarantine: %w", err) } + defer func() { + if err := cleanup(); err != nil { + ctxlogrus.Extract(ctx).WithError(err).Error("failed to cleanup quarantine directory") + } + }() objectHash, err := quarantineRepo.ObjectHash(ctx) if err != nil { diff --git a/internal/gitaly/service/operations/rebase_to_ref.go b/internal/gitaly/service/operations/rebase_to_ref.go index ddb8fde1761b64369c6a73ac6350315b3f88390e..56cddcb85b5d4870f7c66f2358e82f16325bb792 100644 --- a/internal/gitaly/service/operations/rebase_to_ref.go +++ b/internal/gitaly/service/operations/rebase_to_ref.go @@ -4,6 +4,7 @@ import ( "context" "errors" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/v16/internal/structerr" @@ -18,10 +19,15 @@ func (s *Server) UserRebaseToRef(ctx context.Context, request *gitalypb.UserReba return nil, structerr.NewInvalidArgument("%w", err) } - quarantineDir, quarantineRepo, err := s.quarantinedRepo(ctx, request.GetRepository()) + quarantineDir, quarantineRepo, cleanup, err := s.quarantinedRepo(ctx, request.GetRepository()) if err != nil { return nil, structerr.NewInternal("creating repo quarantine: %w", err) } + defer func() { + if err := cleanup(); err != nil { + ctxlogrus.Extract(ctx).WithError(err).Error("failed to cleanup quarantine directory") + } + }() oid, err := quarantineRepo.ResolveRevision(ctx, git.Revision(request.GetFirstParentRef())) if err != nil { diff --git a/internal/gitaly/service/operations/revert.go b/internal/gitaly/service/operations/revert.go index 760e625eb704d80417ae622c8b898fc4afbece6d..ab92306d5f6e38446fef3a415ed52d3f836ed243 100644 --- a/internal/gitaly/service/operations/revert.go +++ b/internal/gitaly/service/operations/revert.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/git/gitcmd" "gitlab.com/gitlab-org/gitaly/v16/internal/git/localrepo" @@ -20,10 +21,15 @@ func (s *Server) UserRevert(ctx context.Context, req *gitalypb.UserRevertRequest return nil, structerr.NewInvalidArgument("%w", err) } - quarantineDir, quarantineRepo, err := s.quarantinedRepo(ctx, req.GetRepository()) + quarantineDir, quarantineRepo, cleanup, err := s.quarantinedRepo(ctx, req.GetRepository()) if err != nil { return nil, err } + defer func() { + if err := cleanup(); err != nil { + ctxlogrus.Extract(ctx).WithError(err).Error("failed to cleanup quarantine directory") + } + }() startRevision, err := s.fetchStartRevision(ctx, quarantineRepo, req) if err != nil { diff --git a/internal/gitaly/service/operations/server.go b/internal/gitaly/service/operations/server.go index 2842fb3aa8f9ea502a58412df58752823f99368c..e513d4b7e10a91e837140368649af237495697e7 100644 --- a/internal/gitaly/service/operations/server.go +++ b/internal/gitaly/service/operations/server.go @@ -47,12 +47,12 @@ func NewServer(deps *service.Dependencies) *Server { } } -func (s *Server) quarantinedRepo(ctx context.Context, repo *gitalypb.Repository) (*quarantine.Dir, *localrepo.Repo, error) { - quarantineDir, err := quarantine.New(ctx, repo, s.logger, s.locator) +func (s *Server) quarantinedRepo(ctx context.Context, repo *gitalypb.Repository) (*quarantine.Dir, *localrepo.Repo, func() error, error) { + quarantineDir, cleanup, err := quarantine.New(ctx, repo, s.logger, s.locator) if err != nil { - return nil, nil, structerr.NewInternal("creating object quarantine: %w", err) + return nil, nil, nil, structerr.NewInternal("creating object quarantine: %w", err) } quarantineRepo := s.localRepoFactory.Build(quarantineDir.QuarantinedRepo()) - return quarantineDir, quarantineRepo, nil + return quarantineDir, quarantineRepo, cleanup, nil } diff --git a/internal/gitaly/service/operations/squash.go b/internal/gitaly/service/operations/squash.go index 8310938d694c1a2d61ef9ec87477f2ee791dfa79..8f084570e0056f56eb4d5cb5a0ed11eaa7d57525 100644 --- a/internal/gitaly/service/operations/squash.go +++ b/internal/gitaly/service/operations/squash.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage" @@ -77,10 +78,15 @@ func validateUserSquashRequest(ctx context.Context, locator storage.Locator, req func (s *Server) userSquash(ctx context.Context, req *gitalypb.UserSquashRequest) (string, error) { // All new objects are staged into a quarantine directory first so that we can do // transactional voting before we commit data to disk. - quarantineDir, quarantineRepo, err := s.quarantinedRepo(ctx, req.GetRepository()) + quarantineDir, quarantineRepo, cleanup, err := s.quarantinedRepo(ctx, req.GetRepository()) if err != nil { return "", structerr.NewInternal("creating quarantine: %w", err) } + defer func() { + if err := cleanup(); err != nil { + ctxlogrus.Extract(ctx).WithError(err).Error("failed to cleanup quarantine directory") + } + }() // We need to retrieve the start commit such that we can create the new commit with // all parents of the start commit. diff --git a/internal/gitaly/service/operations/submodules.go b/internal/gitaly/service/operations/submodules.go index cdf9cfce2646f39ec3ed1178a9b4cd94a05c2141..7eee56e83f314280d8c118e7625f8206dbd02c37 100644 --- a/internal/gitaly/service/operations/submodules.go +++ b/internal/gitaly/service/operations/submodules.go @@ -6,6 +6,7 @@ import ( "fmt" "strings" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/hook/updateref" @@ -25,10 +26,15 @@ func (s *Server) UserUpdateSubmodule(ctx context.Context, req *gitalypb.UserUpda return nil, structerr.NewInvalidArgument("%w", err) } - quarantineDir, quarantineRepo, err := s.quarantinedRepo(ctx, req.GetRepository()) + quarantineDir, quarantineRepo, cleanup, err := s.quarantinedRepo(ctx, req.GetRepository()) if err != nil { return nil, err } + defer func() { + if err := cleanup(); err != nil { + ctxlogrus.Extract(ctx).WithError(err).Error("failed to cleanup quarantine directory") + } + }() objectHash, err := quarantineRepo.ObjectHash(ctx) if err != nil { diff --git a/internal/gitaly/service/operations/tags.go b/internal/gitaly/service/operations/tags.go index 29f3c3902fea46de722e403ebbbeabbe39ae7c91..59099e48263d7ca0379f261aa230facd40269ac7 100644 --- a/internal/gitaly/service/operations/tags.go +++ b/internal/gitaly/service/operations/tags.go @@ -7,6 +7,7 @@ import ( "fmt" "regexp" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/git/catfile" "gitlab.com/gitlab-org/gitaly/v16/internal/git/localrepo" @@ -129,10 +130,15 @@ func (s *Server) UserCreateTag(ctx context.Context, req *gitalypb.UserCreateTagR return nil, structerr.NewInvalidArgument("%w", err) } - quarantineDir, quarantineRepo, err := s.quarantinedRepo(ctx, req.GetRepository()) + quarantineDir, quarantineRepo, cleanup, err := s.quarantinedRepo(ctx, req.GetRepository()) if err != nil { return nil, err } + defer func() { + if err := cleanup(); err != nil { + ctxlogrus.Extract(ctx).WithError(err).Error("failed to cleanup quarantine directory") + } + }() objectHash, err := quarantineRepo.ObjectHash(ctx) if err != nil { diff --git a/internal/gitaly/service/operations/user_create_branch.go b/internal/gitaly/service/operations/user_create_branch.go index 5210f445498b01cf7437731bf1f4eb03c9ee10f2..6998279a50ecf36015b7230964ebf2c43f9237e6 100644 --- a/internal/gitaly/service/operations/user_create_branch.go +++ b/internal/gitaly/service/operations/user_create_branch.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/hook/updateref" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage" @@ -34,10 +35,15 @@ func (s *Server) UserCreateBranch(ctx context.Context, req *gitalypb.UserCreateB if err := validateUserCreateBranchRequest(ctx, s.locator, req); err != nil { return nil, structerr.NewInvalidArgument("%w", err) } - quarantineDir, quarantineRepo, err := s.quarantinedRepo(ctx, req.GetRepository()) + quarantineDir, quarantineRepo, cleanup, err := s.quarantinedRepo(ctx, req.GetRepository()) if err != nil { return nil, err } + defer func() { + if err := cleanup(); err != nil { + ctxlogrus.Extract(ctx).WithError(err).Error("failed to cleanup quarantine directory") + } + }() // BEGIN TODO: Uncomment if StartPoint started behaving sensibly // like BranchName. See diff --git a/internal/gitaly/service/operations/user_update_branch.go b/internal/gitaly/service/operations/user_update_branch.go index 08bc7db1a041b3af9f6d34493144cd08d541305c..791f9222978d366ffe72554bbfe55b2a5372c348 100644 --- a/internal/gitaly/service/operations/user_update_branch.go +++ b/internal/gitaly/service/operations/user_update_branch.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/hook/updateref" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage" @@ -63,10 +64,15 @@ func (s *Server) UserUpdateBranch(ctx context.Context, req *gitalypb.UserUpdateB referenceName := git.NewReferenceNameFromBranchName(string(req.GetBranchName())) - quarantineDir, _, err := s.quarantinedRepo(ctx, req.GetRepository()) + quarantineDir, _, cleanup, err := s.quarantinedRepo(ctx, req.GetRepository()) if err != nil { return nil, err } + defer func() { + if err := cleanup(); err != nil { + ctxlogrus.Extract(ctx).WithError(err).Error("failed to cleanup quarantine directory") + } + }() if err := s.updateReferenceWithHooks(ctx, req.GetRepository(), req.GetUser(), quarantineDir, referenceName, newOID, oldOID); err != nil { var customHookErr updateref.CustomHookError diff --git a/internal/gitaly/service/repository/fetch.go b/internal/gitaly/service/repository/fetch.go index 56a5d663dfd50178438ef24db92890a7460afd41..ccc127e3c8012f8a1397a13229a5bfaa2b363812 100644 --- a/internal/gitaly/service/repository/fetch.go +++ b/internal/gitaly/service/repository/fetch.go @@ -4,6 +4,7 @@ import ( "context" "errors" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/v16/internal/git/remoterepo" @@ -30,10 +31,15 @@ func (s *server) FetchSourceBranch(ctx context.Context, req *gitalypb.FetchSourc return nil, structerr.NewInvalidArgument("%w", err) } - quarantineDir, targetRepo, err := s.quarantinedRepo(ctx, req.GetRepository()) + quarantineDir, targetRepo, cleanup, err := s.quarantinedRepo(ctx, req.GetRepository()) if err != nil { return nil, err } + defer func() { + if err := cleanup(); err != nil { + ctxlogrus.Extract(ctx).WithError(err).Error("failed to cleanup quarantine directory") + } + }() sourceRepo, err := remoterepo.New(ctx, req.GetSourceRepository(), s.conns) if err != nil { diff --git a/internal/gitaly/service/repository/fetch_remote.go b/internal/gitaly/service/repository/fetch_remote.go index bf50ffd893d45794e4f15e0a284fc027c6604c4e..655a30530987c4abae234fdf90ac010ea266034c 100644 --- a/internal/gitaly/service/repository/fetch_remote.go +++ b/internal/gitaly/service/repository/fetch_remote.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" "gitlab.com/gitlab-org/gitaly/v16/internal/git" "gitlab.com/gitlab-org/gitaly/v16/internal/git/gitcmd" "gitlab.com/gitlab-org/gitaly/v16/internal/git/localrepo" @@ -76,11 +77,11 @@ func (s *server) fetchRemoteAtomic(ctx context.Context, req *gitalypb.FetchRemot return false, false, err } - sshCommand, cleanup, err := gitcmd.BuildSSHInvocation(ctx, s.logger, req.GetSshKey(), req.GetKnownHosts()) + sshCommand, sshCleanup, err := gitcmd.BuildSSHInvocation(ctx, s.logger, req.GetSshKey(), req.GetKnownHosts()) if err != nil { return false, false, err } - defer cleanup() + defer sshCleanup() opts.Env = append(opts.Env, "GIT_SSH_COMMAND="+sshCommand) @@ -88,10 +89,15 @@ func (s *server) fetchRemoteAtomic(ctx context.Context, req *gitalypb.FetchRemot // to be updated, unreachable objects could be left in the repository that would need to be // garbage collected. To be more atomic, a quarantine directory is set up where objects will be // fetched prior to being migrated to the main repository when reference updates are committed. - quarantineDir, err := quarantine.New(ctx, req.GetRepository(), s.logger, s.locator) + quarantineDir, quarantineCleanup, err := quarantine.New(ctx, req.GetRepository(), s.logger, s.locator) if err != nil { return false, false, fmt.Errorf("creating quarantine directory: %w", err) } + defer func() { + if cleanupErr := quarantineCleanup(); cleanupErr != nil { + ctxlogrus.Extract(ctx).WithError(cleanupErr).Error("failed to cleanup quarantine directory") + } + }() quarantineRepo := s.localRepoFactory.Build(quarantineDir.QuarantinedRepo()) if err := quarantineRepo.FetchRemote(ctx, "inmemory", opts); err != nil { diff --git a/internal/gitaly/service/repository/server.go b/internal/gitaly/service/repository/server.go index 7dfa10dd2d9fbb2bc545789b38fb54cb1e41d793..1fc4f90829b9fc284080f19df0f57009a78c1762 100644 --- a/internal/gitaly/service/repository/server.go +++ b/internal/gitaly/service/repository/server.go @@ -71,12 +71,12 @@ func NewServer(deps *service.Dependencies) gitalypb.RepositoryServiceServer { } } -func (s *server) quarantinedRepo(ctx context.Context, repo *gitalypb.Repository) (*quarantine.Dir, *localrepo.Repo, error) { - quarantineDir, err := quarantine.New(ctx, repo, s.logger, s.locator) +func (s *server) quarantinedRepo(ctx context.Context, repo *gitalypb.Repository) (*quarantine.Dir, *localrepo.Repo, func() error, error) { + quarantineDir, cleanup, err := quarantine.New(ctx, repo, s.logger, s.locator) if err != nil { - return nil, nil, structerr.NewInternal("creating object quarantine: %w", err) + return nil, nil, nil, structerr.NewInternal("creating object quarantine: %w", err) } quarantineRepo := s.localRepoFactory.Build(quarantineDir.QuarantinedRepo()) - return quarantineDir, quarantineRepo, nil + return quarantineDir, quarantineRepo, cleanup, nil } diff --git a/internal/gitaly/service/repository/size_test.go b/internal/gitaly/service/repository/size_test.go index 57e5e93a7aa11f0018d9f4aa5756e33c74e44012..24240e7a6120371041e12dac4921dd7319a61940 100644 --- a/internal/gitaly/service/repository/size_test.go +++ b/internal/gitaly/service/repository/size_test.go @@ -228,8 +228,9 @@ func TestGetObjectDirectorySize_quarantine(t *testing.T) { requireObjectDirectorySize(t, ctx, client, repo, 16) - quarantine, err := quarantine.New(ctx, gittest.RewrittenRepository(t, ctx, cfg, repo), logger, locator) + quarantine, cleanup, err := quarantine.New(ctx, gittest.RewrittenRepository(t, ctx, cfg, repo), logger, locator) require.NoError(t, err) + defer func() { _ = cleanup() }() // quarantine.New in Gitaly would receive an already rewritten repository. Gitaly would then calculate // the quarantine directories based on the rewritten relative path. That quarantine would then be looped @@ -276,12 +277,14 @@ func TestGetObjectDirectorySize_quarantine(t *testing.T) { t.Run("quarantined repo with different relative path", func(t *testing.T) { repo1, _ := gittest.CreateRepository(t, ctx, cfg) - quarantine1, err := quarantine.New(ctx, gittest.RewrittenRepository(t, ctx, cfg, repo1), logger, locator) + quarantine1, cleanup1, err := quarantine.New(ctx, gittest.RewrittenRepository(t, ctx, cfg, repo1), logger, locator) require.NoError(t, err) + defer func() { _ = cleanup1() }() repo2, _ := gittest.CreateRepository(t, ctx, cfg) - quarantine2, err := quarantine.New(ctx, gittest.RewrittenRepository(t, ctx, cfg, repo2), logger, locator) + quarantine2, cleanup2, err := quarantine.New(ctx, gittest.RewrittenRepository(t, ctx, cfg, repo2), logger, locator) require.NoError(t, err) + defer func() { _ = cleanup2() }() // We swap out the the object directories of both quarantines. So while both are // valid, we still expect that this RPC call fails because we detect that the diff --git a/internal/tempdir/tempdir.go b/internal/tempdir/tempdir.go index f70c1216ce0aa83c362b12318ebb1aa166530d8c..08d55f8d0f176799ee2a21049127e707c566cf78 100644 --- a/internal/tempdir/tempdir.go +++ b/internal/tempdir/tempdir.go @@ -17,7 +17,6 @@ import ( type Dir struct { logger log.Logger path string - doneCh chan struct{} } // Path returns the absolute path of the temporary directory. @@ -25,54 +24,58 @@ func (d Dir) Path() string { return d.path } -// New returns the path of a new temporary directory for the given storage. The directory is removed -// asynchronously with os.RemoveAll when the context expires. -func New(ctx context.Context, storageName string, logger log.Logger, locator storage.Locator) (Dir, error) { +// New returns the path of a new temporary directory for the given storage and a cleanup function +// that must be called to remove the directory. +func New(ctx context.Context, storageName string, logger log.Logger, locator storage.Locator) (Dir, func() error, error) { return NewWithPrefix(ctx, storageName, "repo", logger, locator) } // NewWithPrefix returns the path of a new temporary directory for the given storage with a specific -// prefix used to create the temporary directory's name. The directory is removed asynchronously -// with os.RemoveAll when the context expires. -func NewWithPrefix(ctx context.Context, storageName, prefix string, logger log.Logger, locator storage.Locator) (Dir, error) { +// prefix used to create the temporary directory's name, and a cleanup function that must be called +// to remove the directory. +func NewWithPrefix(ctx context.Context, storageName, prefix string, logger log.Logger, locator storage.Locator) (Dir, func() error, error) { dir, err := newDirectory(ctx, storageName, prefix, logger, locator) if err != nil { - return Dir{}, err + return Dir{}, nil, err } - go dir.cleanupOnDone(ctx) + cleanup := func() error { + return os.RemoveAll(dir.path) + } - return dir, nil + return dir, cleanup, nil } // NewWithoutContext returns a temporary directory for the given storage suitable which is not -// storage scoped. The temporary directory will thus not get cleaned up when the context expires, -// but instead when the temporary directory is older than MaxAge. +// storage scoped. The temporary directory will thus not get cleaned up automatically. func NewWithoutContext(storageName string, logger log.Logger, locator storage.Locator) (Dir, error) { prefix := fmt.Sprintf("%s-repositories.old.%d.", storageName, time.Now().Unix()) - return newDirectory(context.Background(), storageName, prefix, logger, locator) + dir, err := newDirectory(context.Background(), storageName, prefix, logger, locator) + return dir, err } // NewRepository is the same as New, but it returns a *gitalypb.Repository for the created directory -// as well as the bare path as a string. -func NewRepository(ctx context.Context, storageName string, logger log.Logger, locator storage.Locator) (*gitalypb.Repository, Dir, error) { +// as well as the bare path as a string, and a cleanup function that must be called to remove the directory. +func NewRepository(ctx context.Context, storageName string, logger log.Logger, locator storage.Locator) (*gitalypb.Repository, Dir, func() error, error) { storagePath, err := locator.GetStorageByName(ctx, storageName) if err != nil { - return nil, Dir{}, err + return nil, Dir{}, nil, err } - dir, err := New(ctx, storageName, logger, locator) + dir, cleanup, err := New(ctx, storageName, logger, locator) if err != nil { - return nil, Dir{}, err + return nil, Dir{}, nil, err } newRepo := &gitalypb.Repository{StorageName: storageName} newRepo.RelativePath, err = filepath.Rel(storagePath, dir.Path()) if err != nil { - return nil, Dir{}, err + // Clean up the directory if we fail after creating it + _ = cleanup() + return nil, Dir{}, nil, err } - return newRepo, dir, nil + return newRepo, dir, cleanup, nil } func newDirectory(ctx context.Context, storageName string, prefix string, logger log.Logger, loc storage.Locator) (Dir, error) { @@ -93,20 +96,5 @@ func newDirectory(ctx context.Context, storageName string, prefix string, logger return Dir{ logger: logger, path: tempDir, - doneCh: make(chan struct{}), }, err } - -func (d Dir) cleanupOnDone(ctx context.Context) { - <-ctx.Done() - if err := os.RemoveAll(d.Path()); err != nil { - d.logger.WithError(err).WithField("temporary_directory", d.Path).ErrorContext(ctx, "failed to cleanup temp dir") - } - close(d.doneCh) -} - -// WaitForCleanup waits until the temporary directory got removed via the asynchronous cleanupOnDone -// call. This is mainly intended for use in tests. -func (d Dir) WaitForCleanup() { - <-d.doneCh -} diff --git a/internal/tempdir/tempdir_test.go b/internal/tempdir/tempdir_test.go index bbfc1916826828cd5bdedc32f629ee63cb521562..559fbc4e1fa6cf71c48a73261775487b4514ef0b 100644 --- a/internal/tempdir/tempdir_test.go +++ b/internal/tempdir/tempdir_test.go @@ -1,7 +1,6 @@ package tempdir import ( - "context" "os" "path/filepath" "testing" @@ -15,13 +14,14 @@ import ( ) func TestNewRepositorySuccess(t *testing.T) { - ctx, cancel := context.WithCancel(testhelper.Context(t)) + ctx := testhelper.Context(t) cfg := testcfg.Build(t) locator := config.NewLocator(cfg) - repo, tempDir, err := NewRepository(ctx, cfg.Storages[0].Name, testhelper.NewLogger(t), locator) + repo, tempDir, cleanup, err := NewRepository(ctx, cfg.Storages[0].Name, testhelper.NewLogger(t), locator) require.NoError(t, err) + require.Equal(t, cfg.Storages[0].Name, repo.GetStorageName()) require.Contains(t, repo.GetRelativePath(), tmpRootPrefix) @@ -33,9 +33,8 @@ func TestNewRepositorySuccess(t *testing.T) { require.DirExists(t, tempDir.Path()) - cancel() // This should trigger async removal of the temporary directory - tempDir.WaitForCleanup() - + // Directory should be removed after cleanup + require.NoError(t, cleanup()) require.NoDirExists(t, tempDir.Path()) } @@ -44,14 +43,15 @@ func TestNewWithPrefix(t *testing.T) { locator := config.NewLocator(cfg) ctx := testhelper.Context(t) - dir, err := NewWithPrefix(ctx, cfg.Storages[0].Name, "foobar-", testhelper.NewLogger(t), locator) + dir, cleanup, err := NewWithPrefix(ctx, cfg.Storages[0].Name, "foobar-", testhelper.NewLogger(t), locator) require.NoError(t, err) + defer func() { _ = cleanup() }() require.Contains(t, dir.Path(), "/foobar-") } func TestNewAsRepositoryFailStorageUnknown(t *testing.T) { ctx := testhelper.Context(t) - _, err := New(ctx, "does-not-exist", testhelper.NewLogger(t), config.NewLocator(config.Cfg{})) + _, _, err := New(ctx, "does-not-exist", testhelper.NewLogger(t), config.NewLocator(config.Cfg{})) require.Error(t, err) }