1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
package repository
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
gitalyerrors "gitlab.com/gitlab-org/gitaly/v15/internal/errors"
"gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/transaction"
"gitlab.com/gitlab-org/gitaly/v15/internal/helper"
"gitlab.com/gitlab-org/gitaly/v15/internal/safe"
"gitlab.com/gitlab-org/gitaly/v15/internal/transaction/txinfo"
"gitlab.com/gitlab-org/gitaly/v15/internal/transaction/voting"
"gitlab.com/gitlab-org/gitaly/v15/proto/go/gitalypb"
)
func (s *server) RemoveRepository(ctx context.Context, in *gitalypb.RemoveRepositoryRequest) (*gitalypb.RemoveRepositoryResponse, error) {
repo := in.GetRepository()
if repo == nil {
return nil, helper.ErrInvalidArgument(gitalyerrors.ErrEmptyRepository)
}
path, err := s.locator.GetPath(repo)
if err != nil {
return nil, helper.ErrInternal(err)
}
tempDir, err := s.locator.TempDir(repo.GetStorageName())
if err != nil {
return nil, helper.ErrInternalf("temporary directory: %w", err)
}
if err := os.MkdirAll(tempDir, 0o755); err != nil {
return nil, helper.ErrInternalf("create tmp dir: %w", err)
}
base := filepath.Base(path)
destDir := filepath.Join(tempDir, base+"+removed")
// Check whether the repository exists. If not, then there is nothing we can
// remove. Historically, we didn't return an error in this case, which was just
// plain bad RPC design: callers should be able to act on this, and if they don't
// care they may still just return `NotFound` errors.
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return nil, helper.ErrNotFoundf("repository does not exist")
}
return nil, helper.ErrInternalf("statting repository: %w", err)
}
locker, err := safe.NewLockingFileWriter(path)
if err != nil {
return nil, helper.ErrInternalf("creating locker: %w", err)
}
defer func() {
if err := locker.Close(); err != nil {
ctxlogrus.Extract(ctx).Error("closing repository locker: %w", err)
}
}()
// Lock the repository such that it cannot be created or removed by any concurrent
// RPC call.
if err := locker.Lock(); err != nil {
if errors.Is(err, safe.ErrFileAlreadyLocked) {
return nil, helper.ErrFailedPreconditionf("repository is already locked")
}
return nil, helper.ErrInternalf("locking repository for removal: %w", err)
}
// Recheck whether the repository still exists after we have taken the lock. It
// could be a concurrent RPC call removed the repository while we have not yet been
// holding the lock.
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return nil, helper.ErrNotFoundf("repository was concurrently removed")
}
return nil, helper.ErrInternalf("re-statting repository: %w", err)
}
if err := s.voteOnAction(ctx, repo, voting.Prepared); err != nil {
return nil, helper.ErrInternalf("vote on rename: %w", err)
}
// We move the repository into our temporary directory first before we start to
// delete it. This is done such that we don't leave behind a partially-removed and
// thus likely corrupt repository.
if err := os.Rename(path, destDir); err != nil {
return nil, helper.ErrInternalf("staging repository for removal: %w", err)
}
if err := os.RemoveAll(destDir); err != nil {
return nil, helper.ErrInternalf("removing repository: %w", err)
}
if err := s.voteOnAction(ctx, repo, voting.Committed); err != nil {
return nil, helper.ErrInternalf("vote on finalizing: %w", err)
}
return &gitalypb.RemoveRepositoryResponse{}, nil
}
func (s *server) voteOnAction(ctx context.Context, repo *gitalypb.Repository, phase voting.Phase) error {
return transaction.RunOnContext(ctx, func(tx txinfo.Transaction) error {
var voteStep string
switch phase {
case voting.Prepared:
voteStep = "pre-remove"
case voting.Committed:
voteStep = "post-remove"
default:
return fmt.Errorf("invalid removal step: %d", phase)
}
vote := fmt.Sprintf("%s %s", voteStep, repo.GetRelativePath())
return s.txManager.Vote(ctx, tx, voting.VoteFromData([]byte(vote)), phase)
})
}
|