mirror of https://github.com/portainer/portainer
190 lines
4.7 KiB
Go
190 lines
4.7 KiB
Go
package datastore
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"time"
|
|
|
|
"github.com/portainer/portainer/api/database/models"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
var backupDefaults = struct {
|
|
backupDir string
|
|
commonDir string
|
|
}{
|
|
"backups",
|
|
"common",
|
|
}
|
|
|
|
//
|
|
// Backup Helpers
|
|
//
|
|
|
|
// createBackupFolders create initial folders for backups
|
|
func (store *Store) createBackupFolders() {
|
|
// create common dir
|
|
commonDir := store.commonBackupDir()
|
|
if exists, _ := store.fileService.FileExists(commonDir); !exists {
|
|
if err := os.MkdirAll(commonDir, 0700); err != nil {
|
|
log.Error().Err(err).Msg("error while creating common backup folder")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (store *Store) databasePath() string {
|
|
return store.connection.GetDatabaseFilePath()
|
|
}
|
|
|
|
func (store *Store) commonBackupDir() string {
|
|
return path.Join(store.connection.GetStorePath(), backupDefaults.backupDir, backupDefaults.commonDir)
|
|
}
|
|
|
|
func (store *Store) copyDBFile(from string, to string) error {
|
|
log.Info().Str("from", from).Str("to", to).Msg("copying DB file")
|
|
|
|
err := store.fileService.Copy(from, to, true)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed")
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// BackupOptions provide a helper to inject backup options
|
|
type BackupOptions struct {
|
|
Version string
|
|
BackupDir string
|
|
BackupFileName string
|
|
BackupPath string
|
|
}
|
|
|
|
// getBackupRestoreOptions returns options to store db at common backup dir location; used by:
|
|
// - db backup prior to version upgrade
|
|
// - db rollback
|
|
func getBackupRestoreOptions(backupDir string) *BackupOptions {
|
|
return &BackupOptions{
|
|
BackupDir: backupDir, //connection.commonBackupDir(),
|
|
BackupFileName: beforePortainerVersionUpgradeBackup,
|
|
}
|
|
}
|
|
|
|
// Backup current database with default options
|
|
func (store *Store) Backup(version *models.Version) (string, error) {
|
|
if version == nil {
|
|
return store.backupWithOptions(nil)
|
|
}
|
|
|
|
return store.backupWithOptions(&BackupOptions{
|
|
Version: version.SchemaVersion,
|
|
})
|
|
}
|
|
|
|
func (store *Store) setupOptions(options *BackupOptions) *BackupOptions {
|
|
if options == nil {
|
|
options = &BackupOptions{}
|
|
}
|
|
if options.Version == "" {
|
|
v, err := store.VersionService.Version()
|
|
if err != nil {
|
|
options.Version = ""
|
|
}
|
|
options.Version = v.SchemaVersion
|
|
}
|
|
if options.BackupDir == "" {
|
|
options.BackupDir = store.commonBackupDir()
|
|
}
|
|
if options.BackupFileName == "" {
|
|
options.BackupFileName = fmt.Sprintf("%s.%s.%s", store.connection.GetDatabaseFileName(), options.Version, time.Now().Format("20060102150405"))
|
|
}
|
|
if options.BackupPath == "" {
|
|
options.BackupPath = path.Join(options.BackupDir, options.BackupFileName)
|
|
}
|
|
return options
|
|
}
|
|
|
|
// BackupWithOptions backup current database with options
|
|
func (store *Store) backupWithOptions(options *BackupOptions) (string, error) {
|
|
log.Info().Msg("creating DB backup")
|
|
|
|
store.createBackupFolders()
|
|
|
|
options = store.setupOptions(options)
|
|
dbPath := store.databasePath()
|
|
|
|
if err := store.Close(); err != nil {
|
|
return options.BackupPath, fmt.Errorf(
|
|
"error closing datastore before creating backup: %w",
|
|
err,
|
|
)
|
|
}
|
|
|
|
if err := store.copyDBFile(dbPath, options.BackupPath); err != nil {
|
|
return options.BackupPath, err
|
|
}
|
|
|
|
if _, err := store.Open(); err != nil {
|
|
return options.BackupPath, fmt.Errorf(
|
|
"error opening datastore after creating backup: %w",
|
|
err,
|
|
)
|
|
}
|
|
|
|
return options.BackupPath, 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 {
|
|
options = store.setupOptions(options)
|
|
|
|
// Check if backup file exist before restoring
|
|
_, err := os.Stat(options.BackupPath)
|
|
if os.IsNotExist(err) {
|
|
log.Error().Str("path", options.BackupPath).Err(err).Msg("backup file to restore does not exist %s")
|
|
|
|
return err
|
|
}
|
|
|
|
err = store.Close()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("error while closing store before restore")
|
|
|
|
return err
|
|
}
|
|
|
|
log.Info().Msg("restoring DB backup")
|
|
err = store.copyDBFile(options.BackupPath, store.databasePath())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = store.Open()
|
|
return err
|
|
}
|
|
|
|
// RemoveWithOptions removes backup database based on supplied options
|
|
func (store *Store) removeWithOptions(options *BackupOptions) error {
|
|
log.Info().Msg("removing DB backup")
|
|
|
|
options = store.setupOptions(options)
|
|
_, err := os.Stat(options.BackupPath)
|
|
|
|
if os.IsNotExist(err) {
|
|
log.Error().Str("path", options.BackupPath).Err(err).Msg("backup file to remove does not exist")
|
|
return err
|
|
}
|
|
|
|
log.Info().Str("path", options.BackupPath).Msg("removing DB file")
|
|
err = os.Remove(options.BackupPath)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed")
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|