2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-26 06:46:34 +00:00

upgrade_repo_v2: Use atomic replace for supported backends

This commit is contained in:
Michael Eischer 2022-05-01 20:07:29 +02:00
parent 7559d2f105
commit e36a40db10
13 changed files with 82 additions and 17 deletions

View File

@ -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

View File

@ -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))

View File

@ -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)
} }

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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() {
err := repo.Backend().Remove(ctx, h) // remove the original file for backends which do not support atomic overwriting
if err != nil { err := repo.Backend().Remove(ctx, h)
return fmt.Errorf("remove config failed: %w", err) if err != nil {
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)
} }

View File

@ -11,18 +11,19 @@ import (
// Backend implements a mock backend. // Backend implements a mock backend.
type Backend struct { type Backend struct {
CloseFn func() error CloseFn func() error
IsNotExistFn func(err error) bool IsNotExistFn func(err error) bool
SaveFn func(ctx context.Context, h restic.Handle, rd restic.RewindReader) error SaveFn func(ctx context.Context, h restic.Handle, rd restic.RewindReader) error
OpenReaderFn func(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) OpenReaderFn func(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error)
StatFn func(ctx context.Context, h restic.Handle) (restic.FileInfo, error) StatFn func(ctx context.Context, h restic.Handle) (restic.FileInfo, error)
ListFn func(ctx context.Context, t restic.FileType, fn func(restic.FileInfo) error) error ListFn func(ctx context.Context, t restic.FileType, fn func(restic.FileInfo) error) error
RemoveFn func(ctx context.Context, h restic.Handle) error RemoveFn func(ctx context.Context, h restic.Handle) error
TestFn func(ctx context.Context, h restic.Handle) (bool, error) TestFn func(ctx context.Context, h restic.Handle) (bool, error)
DeleteFn func(ctx context.Context) error DeleteFn func(ctx context.Context) error
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 {

View File

@ -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)