diff --git a/automatic_updates.services.yml b/automatic_updates.services.yml index ce546a1feb02a176539052c5c3c5565153b16d90..67437b6e003348a5194f6bb63d696bdfd65137cf 100644 --- a/automatic_updates.services.yml +++ b/automatic_updates.services.yml @@ -129,3 +129,7 @@ services: - '@string_translation' tags: - { name: event_subscriber } + automatic_updates.validator.target_release: + class: \Drupal\automatic_updates\Validator\UpdateReleaseValidator + tags: + - { name: event_subscriber } diff --git a/src/Validator/UpdateReleaseValidator.php b/src/Validator/UpdateReleaseValidator.php new file mode 100644 index 0000000000000000000000000000000000000000..d501d1cda78fad7d7026eb4880f39e2b181fc559 --- /dev/null +++ b/src/Validator/UpdateReleaseValidator.php @@ -0,0 +1,56 @@ +<?php + +namespace Drupal\automatic_updates\Validator; + +use Drupal\automatic_updates\ProjectInfo; +use Drupal\automatic_updates\Updater; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\package_manager\Event\PreCreateEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Validates that the target release of Drupal core is secure and supported. + */ +class UpdateReleaseValidator implements EventSubscriberInterface { + + use StringTranslationTrait; + + /** + * Checks that the target version of Drupal core is secure and supported. + * + * @param \Drupal\package_manager\Event\PreCreateEvent $event + * The event object. + */ + public function checkRelease(PreCreateEvent $event): void { + $stage = $event->getStage(); + // This check only works with Automatic Updates. + if (!$stage instanceof Updater) { + return; + } + + $package_versions = $stage->getPackageVersions(); + // The updater will only update Drupal core, so all production dependencies + // will be Drupal core packages. + $target_version = reset($package_versions['production']); + + // If the target version isn't in the list of installable releases, then it + // isn't secure and supported and we should flag an error. + $releases = (new ProjectInfo('drupal'))->getInstallableReleases(); + if (empty($releases) || !array_key_exists($target_version, $releases)) { + $message = $this->t('Cannot update Drupal core to @target_version because it is not in the list of installable releases.', [ + '@target_version' => $target_version, + ]); + $event->addError([$message]); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ + PreCreateEvent::class => 'checkRelease', + ]; + } + +} diff --git a/tests/src/Kernel/ReadinessValidation/ReadinessValidationManagerTest.php b/tests/src/Kernel/ReadinessValidation/ReadinessValidationManagerTest.php index c9a09cb821e3ad6640a967a3f9f73cb9563790cb..add1626a4ed1933f19f5830a50b7727d71d02efd 100644 --- a/tests/src/Kernel/ReadinessValidation/ReadinessValidationManagerTest.php +++ b/tests/src/Kernel/ReadinessValidation/ReadinessValidationManagerTest.php @@ -219,12 +219,8 @@ class ReadinessValidationManagerTest extends AutomaticUpdatesKernelTestBase { * Tests that stored validation results are deleted after an update. */ public function testStoredResultsDeletedPostApply(): void { - $this->container->get('module_installer') - ->install(['automatic_updates']); - - // Ensure there's a simulated core release to update to. + $this->enableModules(['automatic_updates']); $this->setCoreVersion('9.8.1'); - $this->setReleaseMetadata(__DIR__ . '/../../../fixtures/release-history/drupal.9.8.2.xml'); // The readiness checker should raise a warning, so that the update is not // blocked or aborted. diff --git a/tests/src/Kernel/ReadinessValidation/SettingsValidatorTest.php b/tests/src/Kernel/ReadinessValidation/SettingsValidatorTest.php index 30493cecf7d6ed2a66c349ff52761a86186a7057..dff44ec010b88f503058d7f0682c3cb2fa574481 100644 --- a/tests/src/Kernel/ReadinessValidation/SettingsValidatorTest.php +++ b/tests/src/Kernel/ReadinessValidation/SettingsValidatorTest.php @@ -51,7 +51,7 @@ class SettingsValidatorTest extends AutomaticUpdatesKernelTestBase { $this->assertCheckerResultsFromManager($expected_results, TRUE); try { $this->container->get('automatic_updates.updater')->begin([ - 'drupal' => '9.8.1', + 'drupal' => '9.8.2', ]); // If there was no exception, ensure we're not expecting any errors. $this->assertSame([], $expected_results); diff --git a/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php b/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php index edc1146f48ae9a24a61fe788442204d72c0d0e6f..eef8b02e2e5884b81a6e2124327f09ba05836a4e 100644 --- a/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php +++ b/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php @@ -61,7 +61,7 @@ class StagedProjectsValidatorTest extends AutomaticUpdatesKernelTestBase { } $updater = $this->container->get('automatic_updates.updater'); - $stage_id = $updater->begin(['drupal' => '9.8.1']); + $stage_id = $updater->begin(['drupal' => '9.8.2']); if ($stage_dir_exists) { // Copy the fixture's staging directory into a subdirectory using the // stage ID as the directory name. diff --git a/tests/src/Kernel/ReadinessValidation/UpdateReleaseValidatorTest.php b/tests/src/Kernel/ReadinessValidation/UpdateReleaseValidatorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4234d103dcdedd0793cbb05104ed6d34e8bdc908 --- /dev/null +++ b/tests/src/Kernel/ReadinessValidation/UpdateReleaseValidatorTest.php @@ -0,0 +1,40 @@ +<?php + +namespace Drupal\Tests\automatic_updates\Kernel\ReadinessValidation; + +use Drupal\automatic_updates\Exception\UpdateException; +use Drupal\package_manager\ValidationResult; +use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase; + +/** + * @covers \Drupal\automatic_updates\Validator\UpdateReleaseValidator + * + * @group automatic_updates + */ +class UpdateReleaseValidatorTest extends AutomaticUpdatesKernelTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['automatic_updates']; + + /** + * Tests that an error is raised when trying to update to an unknown release. + */ + public function testUnknownReleaseRaisesError(): void { + $result = ValidationResult::createError([ + 'Cannot update Drupal core to 9.8.99 because it is not in the list of installable releases.', + ]); + + try { + $this->container->get('automatic_updates.updater')->begin([ + 'drupal' => '9.8.99', + ]); + $this->fail('Expected an exception to be thrown, but it was not.'); + } + catch (UpdateException $e) { + $this->assertValidationResultsEqual([$result], $e->getResults()); + } + } + +} diff --git a/tests/src/Kernel/UpdaterTest.php b/tests/src/Kernel/UpdaterTest.php index f48cb59281010166b86813aa2d3d7b3e7f536647..8dbaeaafb620c27f283bbfd8322be322206c41ce 100644 --- a/tests/src/Kernel/UpdaterTest.php +++ b/tests/src/Kernel/UpdaterTest.php @@ -34,6 +34,9 @@ class UpdaterTest extends AutomaticUpdatesKernelTestBase { * Tests that correct versions are staged after calling ::begin(). */ public function testCorrectVersionsStaged(): void { + // Simulate that we're running Drupal 9.8.0 and a security update to 9.8.1 + // is available. + $this->setCoreVersion('9.8.0'); $this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.9.8.1-security.xml'); // Create a user who will own the stage even after the container is rebuilt.