faa04c188b
* refactor backup Update upgrade texts * Restore Failed Upgrade to EE to initial CE version * Store version before upgrading * Check rollback command line * Fix version display * Update template url only for CE 1.xx * Fix comments * revert go modules * remove duplicate migration * remove unused files
182 lines
4.4 KiB
Go
182 lines
4.4 KiB
Go
package bolt
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"time"
|
|
|
|
portainer "github.com/portainer/portainer/api"
|
|
plog "github.com/portainer/portainer/api/bolt/log"
|
|
)
|
|
|
|
var backupDefaults = struct {
|
|
backupDir string
|
|
editions []string
|
|
databaseFileName string
|
|
}{
|
|
"backups",
|
|
[]string{"CE", "BE", "EE"},
|
|
databaseFileName,
|
|
}
|
|
|
|
var backupLog = plog.NewScopedLog("bolt, backup")
|
|
|
|
//
|
|
// Backup Helpers
|
|
//
|
|
|
|
// createBackupFolders create initial folders for backups
|
|
func (store *Store) createBackupFolders() {
|
|
for _, e := range backupDefaults.editions {
|
|
|
|
p := path.Join(store.path, backupDefaults.backupDir, e)
|
|
|
|
if exists, _ := store.fileService.FileExists(p); !exists {
|
|
err := os.MkdirAll(p, 0700)
|
|
if err != nil {
|
|
backupLog.Error("Error while creating backup folders", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (store *Store) databasePath() string {
|
|
return path.Join(store.path, databaseFileName)
|
|
}
|
|
|
|
func (store *Store) editionBackupDir(edition portainer.SoftwareEdition) string {
|
|
return path.Join(store.path, backupDefaults.backupDir, edition.GetEditionLabel())
|
|
}
|
|
|
|
func (store *Store) copyDBFile(from string, to string) error {
|
|
backupLog.Info(fmt.Sprintf("Copying db file from %s to %s", from, to))
|
|
err := store.fileService.Copy(from, to, true)
|
|
if err != nil {
|
|
backupLog.Error("Failed", err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// BackupOptions provide a helper to inject backup options
|
|
type BackupOptions struct {
|
|
Edition portainer.SoftwareEdition
|
|
Version int
|
|
BackupDir string
|
|
BackupFileName string
|
|
BackupPath string
|
|
}
|
|
|
|
func (store *Store) setupOptions(options *BackupOptions) *BackupOptions {
|
|
if options == nil {
|
|
options = &BackupOptions{}
|
|
}
|
|
if options.Edition == 0 {
|
|
options.Edition = store.edition()
|
|
}
|
|
if options.Version == 0 {
|
|
options.Version, _ = store.version()
|
|
}
|
|
if options.BackupDir == "" {
|
|
options.BackupDir = store.editionBackupDir(options.Edition)
|
|
}
|
|
if options.BackupFileName == "" {
|
|
options.BackupFileName = fmt.Sprintf("%s.%s.%s", backupDefaults.databaseFileName, fmt.Sprintf("%03d", options.Version), time.Now().Format("20060102150405"))
|
|
}
|
|
if options.BackupPath == "" {
|
|
options.BackupPath = path.Join(options.BackupDir, options.BackupFileName)
|
|
}
|
|
return options
|
|
}
|
|
|
|
func (store *Store) listEditionBackups(edition portainer.SoftwareEdition) ([]string, error) {
|
|
var fileNames = []string{}
|
|
|
|
files, err := ioutil.ReadDir(store.editionBackupDir(edition))
|
|
|
|
if err != nil {
|
|
backupLog.Error("Error while retrieving backup files", err)
|
|
return fileNames, err
|
|
}
|
|
|
|
for _, f := range files {
|
|
fileNames = append(fileNames, f.Name())
|
|
}
|
|
|
|
return fileNames, nil
|
|
}
|
|
|
|
func (store *Store) lastestEditionBackup() (string, error) {
|
|
edition := store.edition()
|
|
|
|
files, err := store.listEditionBackups(edition)
|
|
if err != nil {
|
|
backupLog.Error("Error while retrieving backup files", err)
|
|
return "", err
|
|
}
|
|
|
|
if len(files) == 0 {
|
|
return "", nil
|
|
}
|
|
|
|
return files[len(files)-1], nil
|
|
}
|
|
|
|
// BackupWithOptions backup current database with options
|
|
func (store *Store) BackupWithOptions(options *BackupOptions) (string, error) {
|
|
backupLog.Info("creating db backup")
|
|
store.createBackupFolders()
|
|
|
|
options = store.setupOptions(options)
|
|
|
|
return options.BackupPath, store.copyDBFile(store.databasePath(), options.BackupPath)
|
|
}
|
|
|
|
// Backup current database with default options
|
|
func (store *Store) Backup() (string, error) {
|
|
return store.BackupWithOptions(nil)
|
|
}
|
|
|
|
// RestoreWithOptions previously saved backup for the current Edition with options
|
|
// Restore strategies:
|
|
// - default: restore latest from current edition
|
|
// - restore a specific
|
|
func (store *Store) RestoreWithOptions(options *BackupOptions) error {
|
|
|
|
// Check if backup file exist before restoring
|
|
|
|
options = store.setupOptions(options)
|
|
|
|
_, err := os.Stat(options.BackupPath)
|
|
|
|
if os.IsNotExist(err) {
|
|
backupLog.Error(fmt.Sprintf("Backup file to restore does not exist %s", options.BackupPath), err)
|
|
return err
|
|
}
|
|
|
|
err = store.Close()
|
|
if err != nil {
|
|
backupLog.Error("Error while closing store before restore", err)
|
|
return err
|
|
}
|
|
backupLog.Info("Restoring db backup")
|
|
err = store.copyDBFile(options.BackupPath, store.databasePath())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return store.Open()
|
|
}
|
|
|
|
// Restore previously saved backup for the current Edition with default options
|
|
func (store *Store) Restore() error {
|
|
var options = &BackupOptions{}
|
|
var err error
|
|
options.BackupFileName, err = store.lastestEditionBackup()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return store.RestoreWithOptions(options)
|
|
|
|
}
|