diff --git a/php/EE/Migration/CustomContainerMigrations.php b/php/EE/Migration/CustomContainerMigrations.php index 39f5c86e5..1fcb1f8bd 100644 --- a/php/EE/Migration/CustomContainerMigrations.php +++ b/php/EE/Migration/CustomContainerMigrations.php @@ -8,6 +8,9 @@ class CustomContainerMigrations { + /** @var array Migrations completed by this invocation, oldest first: name => instance. */ + private static $executed = []; + /** * Executes pending migrations of container. */ @@ -16,6 +19,8 @@ public static function execute_migrations() { Utils\delem_log( 'ee migration start' ); EE::debug( 'Executing custom container migrations' ); + self::$executed = []; + $migrations = self::get_all_migrations(); if ( empty( $migrations ) ) { @@ -35,6 +40,57 @@ public static function execute_migrations() { EE::debug( 'Successfully migrated EasyEngine' ); } + /** + * Reverts the container migrations completed by this invocation, newest first, when a later upgrade step fails. + * + * Their rows are deleted so the next attempt runs them again. Migrations recorded by earlier runs are not touched. + */ + public static function revert_executed_migrations() { + + $executed = array_reverse( self::$executed, true ); + self::$executed = []; + + if ( empty( $executed ) ) { + return; + } + + // On a fresh install they are no-ops by design, and older down() methods assume an upgrade. + if ( ! \EE\Model\Option::get( 'version' ) ) { + EE::debug( 'Fresh install: not reverting container migrations' ); + return; + } + + foreach ( $executed as $name => $migration ) { + EE::debug( "Reverting: $name" ); + $reverted = true; + try { + $migration->down(); + } catch ( \Throwable $e ) { + $reverted = false; + EE::warning( "Could not revert container migration $name: " . $e->getMessage() ); + } + + // Deleted even if down() failed: migrations are idempotent, and a retry must run it again. + try { + foreach ( Migration::where( 'migration', $name ) as $row ) { + $row->delete(); + } + EE::debug( $reverted ? "Reverted: $name" : "Removed the migrations row of $name" ); + } catch ( \Throwable $e ) { + EE::warning( "Could not delete the migrations row of $name: " . $e->getMessage() ); + } + } + } + + /** + * Keeps the container migrations completed by this invocation: a later failure no longer reverts them. + */ + public static function keep_executed_migrations() { + + self::$executed = []; + EE::debug( 'Keeping the container migrations of this run' ); + } + /** * @return array of available migrations */ @@ -130,6 +186,7 @@ private static function execute_migration_stack( $migrations ) { ] ); $migration->status = 'complete'; + self::$executed[ $migrations[0] ] = $migration; EE::debug( "Migrated: $migrations[0]" ); $remaining_migrations = array_splice( $migrations, 1, count( $migrations ) ); self::execute_migration_stack( $remaining_migrations ); @@ -144,6 +201,7 @@ private static function execute_migration_stack( $migrations ) { $migration->down(); $migrated[0]->delete(); } + unset( self::$executed[ $migrations[0] ] ); EE::debug( "Reverted: $migrations[0]" ); throw $e; diff --git a/php/EE/Runner.php b/php/EE/Runner.php index 8a686e9f3..f31ca9a89 100644 --- a/php/EE/Runner.php +++ b/php/EE/Runner.php @@ -170,8 +170,11 @@ private function migrate() { $rsp = new \EE\RevertableStepProcessor(); $rsp->add_step( 'ee-db-migrations', 'EE\Migration\Executor::execute_migrations' ); - $rsp->add_step( 'ee-custom-container-migrations', 'EE\Migration\CustomContainerMigrations::execute_migrations' ); + $rsp->add_step( 'ee-custom-container-migrations', 'EE\Migration\CustomContainerMigrations::execute_migrations', 'EE\Migration\CustomContainerMigrations::revert_executed_migrations' ); $rsp->add_step( 'ee-docker-image-migrations', 'EE\Migration\Containers::start_container_migration' ); + // The new images run from here on and have no undo, so a later failure must not revert the container migrations onto them. + $rsp->add_step( 'ee-keep-container-migrations', 'EE\Migration\CustomContainerMigrations::keep_executed_migrations' ); + $rsp->add_step( 'ee-after-docker-image-migrations', 'EE::do_hook', null, [ 'after_docker_image_migration' ] ); $rsp->add_step( 'ee-update-docker-compose', 'EE\Migration\Containers::update_docker_compose' ); $rsp->add_step( 'ee-update-cron-config', 'EE\Cron\Utils\update_cron_config' ); $rsp->add_step( 'ee-setup-logrotate', 'EE\Logrotate\Utils::setup_logrotate' );