2022-03-31 19:12:13 +00:00
package migrations
import (
"context"
"fmt"
2022-03-31 19:29:19 +00:00
"io"
"io/ioutil"
"os"
"path/filepath"
2022-03-31 19:12:13 +00:00
"github.com/restic/restic/internal/restic"
)
func init ( ) {
register ( & UpgradeRepoV2 { } )
}
2022-04-03 12:48:39 +00:00
type UpgradeRepoV2Error struct {
UploadNewConfigError error
ReuploadOldConfigError error
BackupFilePath string
}
func ( err * UpgradeRepoV2Error ) Error ( ) string {
if err . ReuploadOldConfigError != nil {
return fmt . Sprintf ( "error uploading config (%v), re-uploading old config filed failed as well (%v), but there is a backup of the config file in %v" , err . UploadNewConfigError , err . ReuploadOldConfigError , err . BackupFilePath )
}
return fmt . Sprintf ( "error uploading config (%v), re-uploaded old config was successful, there is a backup of the config file in %v" , err . UploadNewConfigError , err . BackupFilePath )
}
func ( err * UpgradeRepoV2Error ) Unwrap ( ) error {
// consider the original upload error as the primary cause
return err . UploadNewConfigError
}
2022-03-31 19:12:13 +00:00
type UpgradeRepoV2 struct { }
func ( * UpgradeRepoV2 ) Name ( ) string {
return "upgrade_repo_v2"
}
func ( * UpgradeRepoV2 ) Desc ( ) string {
return "upgrade a repository to version 2"
}
func ( * UpgradeRepoV2 ) Check ( ctx context . Context , repo restic . Repository ) ( bool , error ) {
isV1 := repo . Config ( ) . Version == 1
return isV1 , nil
}
2022-05-01 18:16:49 +00:00
func ( * UpgradeRepoV2 ) RepoCheckOptions ( ) * RepositoryCheckOptions {
return & RepositoryCheckOptions { }
}
2022-03-31 19:29:19 +00:00
func ( * UpgradeRepoV2 ) upgrade ( ctx context . Context , repo restic . Repository ) error {
2022-03-31 19:12:13 +00:00
h := restic . Handle { Type : restic . ConfigFile }
2022-05-01 18:07:29 +00:00
if ! repo . Backend ( ) . HasAtomicReplace ( ) {
// remove the original file for backends which do not support atomic overwriting
err := repo . Backend ( ) . Remove ( ctx , h )
if err != nil {
return fmt . Errorf ( "remove config failed: %w" , err )
}
2022-03-31 19:12:13 +00:00
}
2022-03-31 19:29:19 +00:00
// upgrade config
cfg := repo . Config ( )
cfg . Version = 2
2022-05-01 18:07:29 +00:00
_ , err := repo . SaveJSONUnpacked ( ctx , restic . ConfigFile , cfg )
2022-03-31 19:12:13 +00:00
if err != nil {
return fmt . Errorf ( "save new config file failed: %w" , err )
}
return nil
}
2022-03-31 19:29:19 +00:00
func ( m * UpgradeRepoV2 ) Apply ( ctx context . Context , repo restic . Repository ) error {
tempdir , err := ioutil . TempDir ( "" , "restic-migrate-upgrade-repo-v2-" )
if err != nil {
return fmt . Errorf ( "create temp dir failed: %w" , err )
}
h := restic . Handle { Type : restic . ConfigFile }
// read raw config file and save it to a temp dir, just in case
var rawConfigFile [ ] byte
err = repo . Backend ( ) . Load ( ctx , h , 0 , 0 , func ( rd io . Reader ) ( err error ) {
rawConfigFile , err = ioutil . ReadAll ( rd )
return err
} )
if err != nil {
return fmt . Errorf ( "load config file failed: %w" , err )
}
2022-04-03 12:48:39 +00:00
backupFileName := filepath . Join ( tempdir , "config" )
err = ioutil . WriteFile ( backupFileName , rawConfigFile , 0600 )
2022-03-31 19:29:19 +00:00
if err != nil {
return fmt . Errorf ( "write config file backup to %v failed: %w" , tempdir , err )
}
// run the upgrade
err = m . upgrade ( ctx , repo )
if err != nil {
2022-04-03 12:48:39 +00:00
// build an error we can return to the caller
repoError := & UpgradeRepoV2Error {
UploadNewConfigError : err ,
BackupFilePath : backupFileName ,
}
2022-03-31 19:29:19 +00:00
// try contingency methods, reupload the original file
_ = repo . Backend ( ) . Remove ( ctx , h )
2022-04-03 12:48:39 +00:00
err = repo . Backend ( ) . Save ( ctx , h , restic . NewByteReader ( rawConfigFile , nil ) )
if err != nil {
repoError . ReuploadOldConfigError = err
2022-03-31 19:29:19 +00:00
}
2022-04-03 12:48:39 +00:00
return repoError
2022-03-31 19:29:19 +00:00
}
_ = os . Remove ( backupFileName )
_ = os . Remove ( tempdir )
return nil
}