github.com/mckael/restic@v0.8.3/internal/migrations/s3_layout.go (about)

     1  package migrations
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  
     9  	"github.com/restic/restic/internal/backend"
    10  	"github.com/restic/restic/internal/backend/s3"
    11  	"github.com/restic/restic/internal/debug"
    12  	"github.com/restic/restic/internal/errors"
    13  	"github.com/restic/restic/internal/restic"
    14  )
    15  
    16  func init() {
    17  	register(&S3Layout{})
    18  }
    19  
    20  // S3Layout migrates a repository on an S3 backend from the "s3legacy" to the
    21  // "default" layout.
    22  type S3Layout struct{}
    23  
    24  // Check tests whether the migration can be applied.
    25  func (m *S3Layout) Check(ctx context.Context, repo restic.Repository) (bool, error) {
    26  	be, ok := repo.Backend().(*s3.Backend)
    27  	if !ok {
    28  		debug.Log("backend is not s3")
    29  		return false, nil
    30  	}
    31  
    32  	if be.Layout.Name() != "s3legacy" {
    33  		debug.Log("layout is not s3legacy")
    34  		return false, nil
    35  	}
    36  
    37  	return true, nil
    38  }
    39  
    40  func retry(max int, fail func(err error), f func() error) error {
    41  	var err error
    42  	for i := 0; i < max; i++ {
    43  		err = f()
    44  		if err == nil {
    45  			return err
    46  		}
    47  		if fail != nil {
    48  			fail(err)
    49  		}
    50  	}
    51  	return err
    52  }
    53  
    54  // maxErrors for retrying renames on s3.
    55  const maxErrors = 20
    56  
    57  func (m *S3Layout) moveFiles(ctx context.Context, be *s3.Backend, l backend.Layout, t restic.FileType) error {
    58  	printErr := func(err error) {
    59  		fmt.Fprintf(os.Stderr, "renaming file returned error: %v\n", err)
    60  	}
    61  
    62  	return be.List(ctx, t, func(fi restic.FileInfo) error {
    63  		h := restic.Handle{Type: t, Name: fi.Name}
    64  		debug.Log("move %v", h)
    65  
    66  		return retry(maxErrors, printErr, func() error {
    67  			return be.Rename(h, l)
    68  		})
    69  	})
    70  
    71  	return nil
    72  }
    73  
    74  // Apply runs the migration.
    75  func (m *S3Layout) Apply(ctx context.Context, repo restic.Repository) error {
    76  	be, ok := repo.Backend().(*s3.Backend)
    77  	if !ok {
    78  		debug.Log("backend is not s3")
    79  		return errors.New("backend is not s3")
    80  	}
    81  
    82  	oldLayout := &backend.S3LegacyLayout{
    83  		Path: be.Path(),
    84  		Join: path.Join,
    85  	}
    86  
    87  	newLayout := &backend.DefaultLayout{
    88  		Path: be.Path(),
    89  		Join: path.Join,
    90  	}
    91  
    92  	be.Layout = oldLayout
    93  
    94  	for _, t := range []restic.FileType{
    95  		restic.SnapshotFile,
    96  		restic.DataFile,
    97  		restic.KeyFile,
    98  		restic.LockFile,
    99  	} {
   100  		err := m.moveFiles(ctx, be, newLayout, t)
   101  		if err != nil {
   102  			return err
   103  		}
   104  	}
   105  
   106  	be.Layout = newLayout
   107  
   108  	return nil
   109  }
   110  
   111  // Name returns the name for this migration.
   112  func (m *S3Layout) Name() string {
   113  	return "s3_layout"
   114  }
   115  
   116  // Desc returns a short description what the migration does.
   117  func (m *S3Layout) Desc() string {
   118  	return "move files from 's3legacy' to the 'default' repository layout"
   119  }