restic/internal/migrations/s3_layout.go

138 lines
2.8 KiB
Go

package migrations
import (
"context"
"fmt"
"os"
"path"
"github.com/restic/restic/internal/backend/layout"
"github.com/restic/restic/internal/backend/s3"
"github.com/restic/restic/internal/cache"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic"
)
func init() {
register(&S3Layout{})
}
// S3Layout migrates a repository on an S3 backend from the "s3legacy" to the
// "default" layout.
type S3Layout struct{}
func toS3Backend(repo restic.Repository) *s3.Backend {
b := repo.Backend()
// unwrap cache
if be, ok := b.(*cache.Backend); ok {
b = be.Backend
}
be, ok := b.(*s3.Backend)
if !ok {
debug.Log("backend is not s3")
return nil
}
return be
}
// Check tests whether the migration can be applied.
func (m *S3Layout) Check(ctx context.Context, repo restic.Repository) (bool, string, error) {
be := toS3Backend(repo)
if be == nil {
debug.Log("backend is not s3")
return false, "backend is not s3", nil
}
if be.Layout.Name() != "s3legacy" {
debug.Log("layout is not s3legacy")
return false, "not using the legacy s3 layout", nil
}
return true, "", nil
}
func (m *S3Layout) RepoCheck() bool {
return false
}
func retry(max int, fail func(err error), f func() error) error {
var err error
for i := 0; i < max; i++ {
err = f()
if err == nil {
return nil
}
if fail != nil {
fail(err)
}
}
return err
}
// maxErrors for retrying renames on s3.
const maxErrors = 20
func (m *S3Layout) moveFiles(ctx context.Context, be *s3.Backend, l layout.Layout, t restic.FileType) error {
printErr := func(err error) {
fmt.Fprintf(os.Stderr, "renaming file returned error: %v\n", err)
}
return be.List(ctx, t, func(fi restic.FileInfo) error {
h := restic.Handle{Type: t, Name: fi.Name}
debug.Log("move %v", h)
return retry(maxErrors, printErr, func() error {
return be.Rename(ctx, h, l)
})
})
}
// Apply runs the migration.
func (m *S3Layout) Apply(ctx context.Context, repo restic.Repository) error {
be := toS3Backend(repo)
if be == nil {
debug.Log("backend is not s3")
return errors.New("backend is not s3")
}
oldLayout := &layout.S3LegacyLayout{
Path: be.Path(),
Join: path.Join,
}
newLayout := &layout.DefaultLayout{
Path: be.Path(),
Join: path.Join,
}
be.Layout = oldLayout
for _, t := range []restic.FileType{
restic.SnapshotFile,
restic.PackFile,
restic.KeyFile,
restic.LockFile,
} {
err := m.moveFiles(ctx, be, newLayout, t)
if err != nil {
return err
}
}
be.Layout = newLayout
return nil
}
// Name returns the name for this migration.
func (m *S3Layout) Name() string {
return "s3_layout"
}
// Desc returns a short description what the migration does.
func (m *S3Layout) Desc() string {
return "move files from 's3legacy' to the 'default' repository layout"
}