mirror of https://github.com/portainer/portainer
				
				
				
			
		
			
				
	
	
		
			149 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			149 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
package datastore
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"runtime/debug"
 | 
						|
 | 
						|
	portainer "github.com/portainer/portainer/api"
 | 
						|
	"github.com/portainer/portainer/api/cli"
 | 
						|
	"github.com/portainer/portainer/api/database/models"
 | 
						|
	dserrors "github.com/portainer/portainer/api/dataservices/errors"
 | 
						|
	"github.com/portainer/portainer/api/datastore/migrator"
 | 
						|
	"github.com/portainer/portainer/api/internal/authorization"
 | 
						|
 | 
						|
	"github.com/pkg/errors"
 | 
						|
	"github.com/rs/zerolog/log"
 | 
						|
)
 | 
						|
 | 
						|
func (store *Store) MigrateData() error {
 | 
						|
	updating, err := store.VersionService.IsUpdating()
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrap(err, "while checking if the store is updating")
 | 
						|
	}
 | 
						|
 | 
						|
	if updating {
 | 
						|
		return dserrors.ErrDatabaseIsUpdating
 | 
						|
	}
 | 
						|
 | 
						|
	// migrate new version bucket if required (doesn't write anything to db yet)
 | 
						|
	version, err := store.getOrMigrateLegacyVersion()
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrap(err, "while migrating legacy version")
 | 
						|
	}
 | 
						|
 | 
						|
	migratorParams := store.newMigratorParameters(version)
 | 
						|
	migrator := migrator.NewMigrator(migratorParams)
 | 
						|
 | 
						|
	if !migrator.NeedsMigration() {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	// before we alter anything in the DB, create a backup
 | 
						|
	_, err = store.Backup("")
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrap(err, "while backing up database")
 | 
						|
	}
 | 
						|
 | 
						|
	err = store.FailSafeMigrate(migrator, version)
 | 
						|
	if err != nil {
 | 
						|
		err = errors.Wrap(err, "failed to migrate database")
 | 
						|
 | 
						|
		log.Warn().Err(err).Msg("migration failed, restoring database to previous version")
 | 
						|
		restoreErr := store.Restore()
 | 
						|
		if restoreErr != nil {
 | 
						|
			return errors.Wrap(restoreErr, "failed to restore database")
 | 
						|
		}
 | 
						|
 | 
						|
		log.Info().Msg("database restored to previous version")
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (store *Store) newMigratorParameters(version *models.Version) *migrator.MigratorParameters {
 | 
						|
	return &migrator.MigratorParameters{
 | 
						|
		CurrentDBVersion:        version,
 | 
						|
		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,
 | 
						|
		SnapshotService:         store.SnapshotService,
 | 
						|
		StackService:            store.StackService,
 | 
						|
		TagService:              store.TagService,
 | 
						|
		TeamMembershipService:   store.TeamMembershipService,
 | 
						|
		UserService:             store.UserService,
 | 
						|
		VersionService:          store.VersionService,
 | 
						|
		FileService:             store.fileService,
 | 
						|
		DockerhubService:        store.DockerHubService,
 | 
						|
		AuthorizationService:    authorization.NewService(store),
 | 
						|
		EdgeStackService:        store.EdgeStackService,
 | 
						|
		EdgeJobService:          store.EdgeJobService,
 | 
						|
		TunnelServerService:     store.TunnelServerService,
 | 
						|
		PendingActionsService:   store.PendingActionsService,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// FailSafeMigrate backup and restore DB if migration fail
 | 
						|
func (store *Store) FailSafeMigrate(migrator *migrator.Migrator, version *models.Version) (err error) {
 | 
						|
	defer func() {
 | 
						|
		if e := recover(); e != nil {
 | 
						|
			// return error with cause and stacktrace (recover() doesn't include a stacktrace)
 | 
						|
			err = fmt.Errorf("%v %s", e, string(debug.Stack()))
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	err = store.VersionService.StoreIsUpdating(true)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrap(err, "while updating the store")
 | 
						|
	}
 | 
						|
 | 
						|
	// now update the version to the new struct (if required)
 | 
						|
	err = store.finishMigrateLegacyVersion(version)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrap(err, "while updating version")
 | 
						|
	}
 | 
						|
 | 
						|
	log.Info().Msg("migrating database from version " + version.SchemaVersion + " to " + portainer.APIVersion)
 | 
						|
 | 
						|
	err = migrator.Migrate()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// Special test code to simulate a failure (used by migrate_data_test.go).  Do not remove...
 | 
						|
	if os.Getenv("PORTAINER_TEST_MIGRATE_FAIL") == "FAIL" {
 | 
						|
		panic("test migration failure")
 | 
						|
	}
 | 
						|
 | 
						|
	err = store.VersionService.StoreIsUpdating(false)
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrap(err, "failed to update the store")
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Rollback to a pre-upgrade backup copy/snapshot of portainer.db
 | 
						|
func (store *Store) connectionRollback(force bool) error {
 | 
						|
	if !force {
 | 
						|
		confirmed, err := cli.Confirm("Are you sure you want to rollback your database to the previous backup?")
 | 
						|
		if err != nil || !confirmed {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	err := store.Restore()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	return store.connection.Close()
 | 
						|
}
 |