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
|
package tempdir
import (
"context"
"fmt"
"os"
"path/filepath"
"time"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
"gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage"
"gitlab.com/gitlab-org/gitaly/v16/internal/helper/perm"
"gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb"
)
// Dir is a storage-scoped temporary directory.
type Dir struct {
path string
doneCh chan struct{}
}
// Path returns the absolute path of the temporary directory.
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, locator storage.Locator) (Dir, error) {
return NewWithPrefix(ctx, storageName, "repo", 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, locator storage.Locator) (Dir, error) {
dir, err := newDirectory(ctx, storageName, prefix, locator)
if err != nil {
return Dir{}, err
}
go dir.cleanupOnDone(ctx)
return dir, 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.
func NewWithoutContext(storageName string, locator storage.Locator) (Dir, error) {
prefix := fmt.Sprintf("%s-repositories.old.%d.", storageName, time.Now().Unix())
return newDirectory(context.Background(), storageName, prefix, locator)
}
// 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, locator storage.Locator) (*gitalypb.Repository, Dir, error) {
storagePath, err := locator.GetStorageByName(storageName)
if err != nil {
return nil, Dir{}, err
}
dir, err := New(ctx, storageName, locator)
if err != nil {
return nil, Dir{}, err
}
newRepo := &gitalypb.Repository{StorageName: storageName}
newRepo.RelativePath, err = filepath.Rel(storagePath, dir.Path())
if err != nil {
return nil, Dir{}, err
}
return newRepo, dir, nil
}
func newDirectory(ctx context.Context, storageName string, prefix string, loc storage.Locator) (Dir, error) {
root, err := loc.TempDir(storageName)
if err != nil {
return Dir{}, fmt.Errorf("temp directory: %w", err)
}
if err := os.MkdirAll(root, perm.PrivateDir); err != nil {
return Dir{}, err
}
tempDir, err := os.MkdirTemp(root, prefix)
if err != nil {
return Dir{}, err
}
return Dir{
path: tempDir,
doneCh: make(chan struct{}),
}, err
}
func (d Dir) cleanupOnDone(ctx context.Context) {
<-ctx.Done()
if err := os.RemoveAll(d.Path()); err != nil {
ctxlogrus.Extract(ctx).WithError(err).Errorf("failed to cleanup temp dir %q", d.path)
}
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
}
|