forked from TrueCloudLab/restic
upgrade_repo_v2: Use atomic replace for supported backends
This commit is contained in:
parent
7559d2f105
commit
e36a40db10
13 changed files with 82 additions and 17 deletions
|
@ -125,6 +125,11 @@ func (be *Backend) Hasher() hash.Hash {
|
||||||
return md5.New()
|
return md5.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasAtomicReplace returns whether Save() can atomically replace files
|
||||||
|
func (be *Backend) HasAtomicReplace() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Path returns the path in the bucket that is used for this backend.
|
// Path returns the path in the bucket that is used for this backend.
|
||||||
func (be *Backend) Path() string {
|
func (be *Backend) Path() string {
|
||||||
return be.prefix
|
return be.prefix
|
||||||
|
|
|
@ -147,6 +147,11 @@ func (be *b2Backend) Hasher() hash.Hash {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasAtomicReplace returns whether Save() can atomically replace files
|
||||||
|
func (be *b2Backend) HasAtomicReplace() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// IsNotExist returns true if the error is caused by a non-existing file.
|
// IsNotExist returns true if the error is caused by a non-existing file.
|
||||||
func (be *b2Backend) IsNotExist(err error) bool {
|
func (be *b2Backend) IsNotExist(err error) bool {
|
||||||
return b2.IsNotExist(errors.Cause(err))
|
return b2.IsNotExist(errors.Cause(err))
|
||||||
|
|
|
@ -67,6 +67,10 @@ func (be *Backend) Hasher() hash.Hash {
|
||||||
return be.b.Hasher()
|
return be.b.Hasher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (be *Backend) HasAtomicReplace() bool {
|
||||||
|
return be.b.HasAtomicReplace()
|
||||||
|
}
|
||||||
|
|
||||||
func (be *Backend) IsNotExist(err error) bool {
|
func (be *Backend) IsNotExist(err error) bool {
|
||||||
return be.b.IsNotExist(err)
|
return be.b.IsNotExist(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -201,6 +201,11 @@ func (be *Backend) Hasher() hash.Hash {
|
||||||
return md5.New()
|
return md5.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasAtomicReplace returns whether Save() can atomically replace files
|
||||||
|
func (be *Backend) HasAtomicReplace() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Path returns the path in the bucket that is used for this backend.
|
// Path returns the path in the bucket that is used for this backend.
|
||||||
func (be *Backend) Path() string {
|
func (be *Backend) Path() string {
|
||||||
return be.prefix
|
return be.prefix
|
||||||
|
|
|
@ -102,6 +102,11 @@ func (b *Local) Hasher() hash.Hash {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasAtomicReplace returns whether Save() can atomically replace files
|
||||||
|
func (b *Local) HasAtomicReplace() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// IsNotExist returns true if the error is caused by a non existing file.
|
// IsNotExist returns true if the error is caused by a non existing file.
|
||||||
func (b *Local) IsNotExist(err error) bool {
|
func (b *Local) IsNotExist(err error) bool {
|
||||||
return errors.Is(err, os.ErrNotExist)
|
return errors.Is(err, os.ErrNotExist)
|
||||||
|
|
|
@ -268,6 +268,11 @@ func (be *MemoryBackend) Hasher() hash.Hash {
|
||||||
return md5.New()
|
return md5.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasAtomicReplace returns whether Save() can atomically replace files
|
||||||
|
func (be *MemoryBackend) HasAtomicReplace() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Delete removes all data in the backend.
|
// Delete removes all data in the backend.
|
||||||
func (be *MemoryBackend) Delete(ctx context.Context) error {
|
func (be *MemoryBackend) Delete(ctx context.Context) error {
|
||||||
be.m.Lock()
|
be.m.Lock()
|
||||||
|
|
|
@ -121,6 +121,12 @@ func (b *Backend) Hasher() hash.Hash {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasAtomicReplace returns whether Save() can atomically replace files
|
||||||
|
func (b *Backend) HasAtomicReplace() bool {
|
||||||
|
// rest-server prevents overwriting
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Save stores data in the backend at the handle.
|
// Save stores data in the backend at the handle.
|
||||||
func (b *Backend) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader) error {
|
func (b *Backend) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader) error {
|
||||||
if err := h.Valid(); err != nil {
|
if err := h.Valid(); err != nil {
|
||||||
|
|
|
@ -269,6 +269,11 @@ func (be *Backend) Hasher() hash.Hash {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasAtomicReplace returns whether Save() can atomically replace files
|
||||||
|
func (be *Backend) HasAtomicReplace() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Path returns the path in the bucket that is used for this backend.
|
// Path returns the path in the bucket that is used for this backend.
|
||||||
func (be *Backend) Path() string {
|
func (be *Backend) Path() string {
|
||||||
return be.cfg.Prefix
|
return be.cfg.Prefix
|
||||||
|
|
|
@ -267,6 +267,12 @@ func (r *SFTP) Hasher() hash.Hash {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasAtomicReplace returns whether Save() can atomically replace files
|
||||||
|
func (r *SFTP) HasAtomicReplace() bool {
|
||||||
|
// we use sftp's 'Rename()' in 'Save()' which does not allow overwriting
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Join joins the given paths and cleans them afterwards. This always uses
|
// Join joins the given paths and cleans them afterwards. This always uses
|
||||||
// forward slashes, which is required by sftp.
|
// forward slashes, which is required by sftp.
|
||||||
func Join(parts ...string) string {
|
func Join(parts ...string) string {
|
||||||
|
|
|
@ -129,6 +129,11 @@ func (be *beSwift) Hasher() hash.Hash {
|
||||||
return md5.New()
|
return md5.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasAtomicReplace returns whether Save() can atomically replace files
|
||||||
|
func (be *beSwift) HasAtomicReplace() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Load runs fn with a reader that yields the contents of the file at h at the
|
// Load runs fn with a reader that yields the contents of the file at h at the
|
||||||
// given offset.
|
// given offset.
|
||||||
func (be *beSwift) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
|
func (be *beSwift) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
|
||||||
|
|
|
@ -53,17 +53,19 @@ func (*UpgradeRepoV2) Check(ctx context.Context, repo restic.Repository) (bool,
|
||||||
func (*UpgradeRepoV2) upgrade(ctx context.Context, repo restic.Repository) error {
|
func (*UpgradeRepoV2) upgrade(ctx context.Context, repo restic.Repository) error {
|
||||||
h := restic.Handle{Type: restic.ConfigFile}
|
h := restic.Handle{Type: restic.ConfigFile}
|
||||||
|
|
||||||
// now remove the original file
|
if !repo.Backend().HasAtomicReplace() {
|
||||||
|
// remove the original file for backends which do not support atomic overwriting
|
||||||
err := repo.Backend().Remove(ctx, h)
|
err := repo.Backend().Remove(ctx, h)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("remove config failed: %w", err)
|
return fmt.Errorf("remove config failed: %w", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// upgrade config
|
// upgrade config
|
||||||
cfg := repo.Config()
|
cfg := repo.Config()
|
||||||
cfg.Version = 2
|
cfg.Version = 2
|
||||||
|
|
||||||
_, err = repo.SaveJSONUnpacked(ctx, restic.ConfigFile, cfg)
|
_, err := repo.SaveJSONUnpacked(ctx, restic.ConfigFile, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("save new config file failed: %w", err)
|
return fmt.Errorf("save new config file failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ type Backend struct {
|
||||||
ConnectionsFn func() uint
|
ConnectionsFn func() uint
|
||||||
LocationFn func() string
|
LocationFn func() string
|
||||||
HasherFn func() hash.Hash
|
HasherFn func() hash.Hash
|
||||||
|
HasAtomicReplaceFn func() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBackend returns new mock Backend instance
|
// NewBackend returns new mock Backend instance
|
||||||
|
@ -66,6 +67,14 @@ func (m *Backend) Hasher() hash.Hash {
|
||||||
return m.HasherFn()
|
return m.HasherFn()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasAtomicReplace returns whether Save() can atomically replace files
|
||||||
|
func (m *Backend) HasAtomicReplace() bool {
|
||||||
|
if m.HasAtomicReplaceFn == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return m.HasAtomicReplaceFn()
|
||||||
|
}
|
||||||
|
|
||||||
// IsNotExist returns true if the error is caused by a missing file.
|
// IsNotExist returns true if the error is caused by a missing file.
|
||||||
func (m *Backend) IsNotExist(err error) bool {
|
func (m *Backend) IsNotExist(err error) bool {
|
||||||
if m.IsNotExistFn == nil {
|
if m.IsNotExistFn == nil {
|
||||||
|
|
|
@ -24,6 +24,9 @@ type Backend interface {
|
||||||
// Hasher may return a hash function for calculating a content hash for the backend
|
// Hasher may return a hash function for calculating a content hash for the backend
|
||||||
Hasher() hash.Hash
|
Hasher() hash.Hash
|
||||||
|
|
||||||
|
// HasAtomicReplace returns whether Save() can atomically replace files
|
||||||
|
HasAtomicReplace() bool
|
||||||
|
|
||||||
// Test a boolean value whether a File with the name and type exists.
|
// Test a boolean value whether a File with the name and type exists.
|
||||||
Test(ctx context.Context, h Handle) (bool, error)
|
Test(ctx context.Context, h Handle) (bool, error)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue