Improved UpdateExecutor

This commit is contained in:
Jan Böhmer 2026-02-02 20:28:17 +01:00
parent 1ccc3ad440
commit 720c1e51e8
2 changed files with 20 additions and 39 deletions

View file

@ -34,6 +34,8 @@ use Symfony\Component\Process\Process;
* *
* This service should primarily be used from CLI commands, not web requests, * This service should primarily be used from CLI commands, not web requests,
* due to the long-running nature of updates and permission requirements. * due to the long-running nature of updates and permission requirements.
*
* For web requests, use startBackgroundUpdate() method.
*/ */
class UpdateExecutor class UpdateExecutor
{ {
@ -46,7 +48,6 @@ class UpdateExecutor
private array $steps = []; private array $steps = [];
private ?string $currentLogFile = null; private ?string $currentLogFile = null;
private CommandRunHelper $commandRunHelper;
public function __construct( public function __construct(
#[Autowire(param: 'kernel.project_dir')] #[Autowire(param: 'kernel.project_dir')]
@ -56,10 +57,10 @@ class UpdateExecutor
private readonly InstallationTypeDetector $installationTypeDetector, private readonly InstallationTypeDetector $installationTypeDetector,
private readonly VersionManagerInterface $versionManager, private readonly VersionManagerInterface $versionManager,
private readonly BackupManager $backupManager, private readonly BackupManager $backupManager,
private readonly CommandRunHelper $commandRunHelper,
#[Autowire(param: 'app.debug_mode')] #[Autowire(param: 'app.debug_mode')]
private readonly bool $debugMode = false, private readonly bool $debugMode = false,
) { ) {
$this->commandRunHelper = new CommandRunHelper($this);
} }
/** /**
@ -75,14 +76,12 @@ class UpdateExecutor
*/ */
public function isLocked(): bool public function isLocked(): bool
{ {
$lockFile = $this->project_dir . '/' . self::LOCK_FILE; // Check if lock is stale (older than 1 hour)
$lockData = $this->getLockInfo();
if (!file_exists($lockFile)) { if ($lockData === null) {
return false; return false;
} }
// Check if lock is stale (older than 1 hour)
$lockData = json_decode(file_get_contents($lockFile), true);
if ($lockData && isset($lockData['started_at'])) { if ($lockData && isset($lockData['started_at'])) {
$startedAt = new \DateTime($lockData['started_at']); $startedAt = new \DateTime($lockData['started_at']);
$now = new \DateTime(); $now = new \DateTime();
@ -100,7 +99,8 @@ class UpdateExecutor
} }
/** /**
* Get lock information. * Get lock information, or null if not locked.
* @return null|array{started_at: string, pid: int, user: string}
*/ */
public function getLockInfo(): ?array public function getLockInfo(): ?array
{ {
@ -110,7 +110,7 @@ class UpdateExecutor
return null; return null;
} }
return json_decode(file_get_contents($lockFile), true); return json_decode(file_get_contents($lockFile), true, 512, JSON_THROW_ON_ERROR);
} }
/** /**
@ -123,6 +123,7 @@ class UpdateExecutor
/** /**
* Get maintenance mode information. * Get maintenance mode information.
* @return null|array{enabled_at: string, reason: string}
*/ */
public function getMaintenanceInfo(): ?array public function getMaintenanceInfo(): ?array
{ {
@ -132,7 +133,7 @@ class UpdateExecutor
return null; return null;
} }
return json_decode(file_get_contents($maintenanceFile), true); return json_decode(file_get_contents($maintenanceFile), true, 512, JSON_THROW_ON_ERROR);
} }
/** /**
@ -157,7 +158,7 @@ class UpdateExecutor
'user' => get_current_user(), 'user' => get_current_user(),
]; ];
$this->filesystem->dumpFile($lockFile, json_encode($lockData, JSON_PRETTY_PRINT)); $this->filesystem->dumpFile($lockFile, json_encode($lockData, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT));
return true; return true;
} }
@ -191,7 +192,7 @@ class UpdateExecutor
'reason' => $reason, 'reason' => $reason,
]; ];
$this->filesystem->dumpFile($maintenanceFile, json_encode($data, JSON_PRETTY_PRINT)); $this->filesystem->dumpFile($maintenanceFile, json_encode($data, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT));
} }
/** /**
@ -574,6 +575,7 @@ class UpdateExecutor
/** /**
* Get list of update log files. * Get list of update log files.
* @return array{file: string, path: string, date: int, size: int}[]
*/ */
public function getUpdateLogs(): array public function getUpdateLogs(): array
{ {
@ -594,28 +596,11 @@ class UpdateExecutor
} }
// Sort by date descending // Sort by date descending
usort($logs, fn($a, $b) => $b['date'] <=> $a['date']); usort($logs, static fn($a, $b) => $b['date'] <=> $a['date']);
return $logs; return $logs;
} }
/**
* Get list of backups.
* @deprecated Use BackupManager::getBackups() directly
*/
public function getBackups(): array
{
return $this->backupManager->getBackups();
}
/**
* Get details about a specific backup file.
* @deprecated Use BackupManager::getBackupDetails() directly
*/
public function getBackupDetails(string $filename): ?array
{
return $this->backupManager->getBackupDetails($filename);
}
/** /**
* Restore from a backup file with maintenance mode and cache clearing. * Restore from a backup file with maintenance mode and cache clearing.
@ -746,8 +731,9 @@ class UpdateExecutor
/** /**
* Save progress to file for web UI polling. * Save progress to file for web UI polling.
* @param array{status: string, target_version: string, create_backup: bool, started_at: string, current_step: int, total_steps: int, step_name: string, step_message: string, steps: array, error: ?string} $progress
*/ */
public function saveProgress(array $progress): void private function saveProgress(array $progress): void
{ {
$progressFile = $this->getProgressFilePath(); $progressFile = $this->getProgressFilePath();
$progressDir = dirname($progressFile); $progressDir = dirname($progressFile);
@ -756,11 +742,12 @@ class UpdateExecutor
$this->filesystem->mkdir($progressDir); $this->filesystem->mkdir($progressDir);
} }
$this->filesystem->dumpFile($progressFile, json_encode($progress, JSON_PRETTY_PRINT)); $this->filesystem->dumpFile($progressFile, json_encode($progress, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT));
} }
/** /**
* Get current update progress from file. * Get current update progress from file.
* @return null|array{status: string, target_version: string, create_backup: bool, started_at: string, current_step: int, total_steps: int, step_name: string, step_message: string, steps: array, error: ?string}
*/ */
public function getProgress(): ?array public function getProgress(): ?array
{ {
@ -770,7 +757,7 @@ class UpdateExecutor
return null; return null;
} }
$data = json_decode(file_get_contents($progressFile), true); $data = json_decode(file_get_contents($progressFile), true, 512, JSON_THROW_ON_ERROR);
// If the progress file is stale (older than 30 minutes), consider it invalid // If the progress file is stale (older than 30 minutes), consider it invalid
if ($data && isset($data['started_at'])) { if ($data && isset($data['started_at'])) {

View file

@ -74,12 +74,6 @@ class UpdateExecutorTest extends KernelTestCase
$this->assertIsArray($logs); $this->assertIsArray($logs);
} }
public function testGetBackupsReturnsArray(): void
{
$backups = $this->updateExecutor->getBackups();
$this->assertIsArray($backups);
}
public function testValidateUpdatePreconditionsReturnsProperStructure(): void public function testValidateUpdatePreconditionsReturnsProperStructure(): void
{ {