* EE-319: backup endpoint (#193) * feat(backup): * add an orbiter to block writes while backup * add backup handler * add an ability to tar.gz a dir * add aes encryption support * EE-320: restore endpoint (#196) * feat(backup): * add restore handler * re-init system state after restore * feat(backup): Update server to respect readonly lock (#199) * feat(backup): EE-322 Add backup and restore screen (#198) Co-authored-by: Simon Meng <simon.meng@portainer.io> * name archive as portainer-backup_yyyy-mm-dd_hh-mm-ss * backup custom templates and edge jobs * restart http and proxy servers after restore to re-init internal state * feat(backup): EE-322 hide password field if password protect toggle is off * feat(backup): EE-322 add tooltip for password field of restore backup * feat(backup): EE-322 wait for backend restart after restoring * Shutdown background go-routines * changed restore err message when cannot extract * fix: symlinks are ignored from backups * replace single admin check with a restartable monitor (#238) * clean log Co-authored-by: Maxime Bajeux <max.bajeux@gmail.com> Co-authored-by: cong meng <mcpacino@gmail.com> Co-authored-by: Simon Meng <simon.meng@portainer.io>
208 lines
6.0 KiB
Go
208 lines
6.0 KiB
Go
package bolt
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
portainer "github.com/portainer/portainer/api"
|
|
errors "github.com/portainer/portainer/api/bolt/errors"
|
|
plog "github.com/portainer/portainer/api/bolt/log"
|
|
"github.com/portainer/portainer/api/bolt/migrator"
|
|
"github.com/portainer/portainer/api/cli"
|
|
"github.com/portainer/portainer/api/internal/authorization"
|
|
)
|
|
|
|
const beforePortainerUpgradeToEEBackup = "portainer.db.before-EE-upgrade"
|
|
|
|
var migrateLog = plog.NewScopedLog("bolt, migrate")
|
|
|
|
// FailSafeMigrate backup and restore DB if migration fail
|
|
func (store *Store) FailSafeMigrate(migrator *migrator.Migrator, version int) error {
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
migrateLog.Info(fmt.Sprintf("Error during migration, recovering [%v]", err))
|
|
store.Restore()
|
|
}
|
|
}()
|
|
return migrator.Migrate(version)
|
|
}
|
|
|
|
// MigrateData automatically migrate the data based on the DBVersion.
|
|
// This process is only triggered on an existing database, not if the database was just created.
|
|
// if force is true, then migrate regardless.
|
|
func (store *Store) MigrateData(force bool) error {
|
|
// 0 – if DB is new then we don't need to migrate any data and just set version and edition to latest EE
|
|
if store.isNew && !force {
|
|
err := store.VersionService.StoreDBVersion(portainer.DBVersionEE)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = store.VersionService.StoreEdition(portainer.PortainerEE)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
migrator, err := store.newMigrator()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if migrator.Edition() == portainer.PortainerCE {
|
|
// backup before migrating
|
|
store.BackupWithOptions(&BackupOptions{
|
|
BackupFileName: beforePortainerUpgradeToEEBackup,
|
|
Edition: portainer.PortainerCE,
|
|
})
|
|
|
|
store.VersionService.StorePreviousDBVersion(migrator.Version())
|
|
|
|
// 1 – We need to migrate DB to latest CE version
|
|
|
|
if migrator.Version() < portainer.DBVersion {
|
|
store.Backup()
|
|
err = store.FailSafeMigrate(migrator, portainer.DBVersion)
|
|
if err != nil {
|
|
store.Restore()
|
|
migrateLog.Error("An error occurred while migrating CE database to latest version", err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if portainer.Edition == portainer.PortainerEE {
|
|
// 2 – if DB is CE Edition we need to upgrade settings to EE
|
|
if migrator.Edition() < portainer.PortainerEE {
|
|
err = migrator.UpgradeToEE()
|
|
if err != nil {
|
|
migrateLog.Error("An error occurred while upgrading database to EE", err)
|
|
store.RollbackFailedUpgradeToEE()
|
|
return err
|
|
}
|
|
|
|
}
|
|
|
|
// 3 – if DB is EE Edition we need to migrate to latest version of EE
|
|
if migrator.Edition() == portainer.PortainerEE && migrator.Version() < portainer.DBVersionEE {
|
|
store.Backup()
|
|
err = store.FailSafeMigrate(migrator, portainer.DBVersionEE)
|
|
if err != nil {
|
|
migrateLog.Error("An error occurred while migrating EE database to latest version", err)
|
|
store.Restore()
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RollbackFailedUpgradeToEE down migrate to previous version
|
|
func (store *Store) RollbackFailedUpgradeToEE() error {
|
|
return store.RestoreWithOptions(&BackupOptions{
|
|
BackupFileName: beforePortainerUpgradeToEEBackup,
|
|
Edition: portainer.PortainerCE,
|
|
})
|
|
}
|
|
|
|
// RollbackToCE rollbacks the store to the current ce version
|
|
func (store *Store) RollbackToCE() error {
|
|
|
|
migrator, err := store.newMigrator()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
migrateLog.Info(fmt.Sprintf("Current Software Edition: %s", migrator.Edition().GetEditionLabel()))
|
|
migrateLog.Info(fmt.Sprintf("Current DB Version: %d", migrator.Version()))
|
|
|
|
if migrator.Edition() == portainer.PortainerCE {
|
|
return errors.ErrMigrationToCE
|
|
}
|
|
|
|
previousVersion, err := store.VersionService.PreviousDBVersion()
|
|
if err != nil {
|
|
migrateLog.Error("An Error occurred with retrieving previous DB version", err)
|
|
return err
|
|
}
|
|
|
|
confirmed, err := cli.Confirm(fmt.Sprintf("Are you sure you want to rollback your database to %d?", previousVersion))
|
|
if err != nil || !confirmed {
|
|
return err
|
|
}
|
|
|
|
if previousVersion < 25 {
|
|
migrator.DowngradeSettingsFrom25()
|
|
}
|
|
|
|
err = store.VersionService.StoreDBVersion(previousVersion)
|
|
if err != nil {
|
|
migrateLog.Error(fmt.Sprintf("An Error occurred with rolling back to CE Edition, DB Version %d", previousVersion), err)
|
|
return err
|
|
}
|
|
|
|
err = store.VersionService.StoreEdition(portainer.PortainerCE)
|
|
if err != nil {
|
|
migrateLog.Error(fmt.Sprintf("An Error occurred with rolling back to CE Edition, DB Version %d", previousVersion), err)
|
|
return err
|
|
}
|
|
|
|
migrateLog.Info(fmt.Sprintf("Rolled back to CE Edition, DB Version %d", previousVersion))
|
|
return nil
|
|
}
|
|
|
|
func (store *Store) newMigrator() (*migrator.Migrator, error) {
|
|
|
|
version, err := store.version()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
edition := store.edition()
|
|
|
|
params := &migrator.Parameters{
|
|
DB: store.connection.DB,
|
|
DatabaseVersion: version,
|
|
CurrentEdition: edition,
|
|
|
|
EndpointGroupService: store.EndpointGroupService,
|
|
EndpointService: store.EndpointService,
|
|
EndpointRelationService: store.EndpointRelationService,
|
|
ExtensionService: store.ExtensionService,
|
|
RegistryService: store.RegistryService,
|
|
ResourceControlService: store.ResourceControlService,
|
|
RoleService: store.RoleService,
|
|
ScheduleService: store.ScheduleService,
|
|
SettingsService: store.SettingsService,
|
|
StackService: store.StackService,
|
|
TagService: store.TagService,
|
|
TeamMembershipService: store.TeamMembershipService,
|
|
UserService: store.UserService,
|
|
VersionService: store.VersionService,
|
|
FileService: store.fileService,
|
|
AuthorizationService: authorization.NewService(store),
|
|
}
|
|
|
|
return migrator.NewMigrator(params), nil
|
|
}
|
|
|
|
// RollbackVersion down migrate to previous version
|
|
func (store *Store) RollbackVersion(version int) error {
|
|
// TODO
|
|
backupLog.NotImplemented("RollbackVersion")
|
|
return nil
|
|
}
|
|
|
|
// RollbackEdition downgrade to previous edition
|
|
func (store *Store) RollbackEdition(edition portainer.SoftwareEdition) error {
|
|
// TODO
|
|
backupLog.NotImplemented("RollbackEdition")
|
|
// Change Edition
|
|
// Migrate Services
|
|
// Restore Latest
|
|
return nil
|
|
}
|