Skip to content
Snippets Groups Projects
Commit 392f8776 authored by Ted Bowman's avatar Ted Bowman
Browse files

Issue #3437409: Don't allow core updates in automatic_updates_extensions

parent 56e2a789
No related branches found
No related tags found
1 merge request!1046Issue #3437409 ForbidCoreChangesValidator.php
Pipeline #136737 passed with warnings
...@@ -6,6 +6,9 @@ services: ...@@ -6,6 +6,9 @@ services:
Drupal\automatic_updates_extensions\Validator\UpdateReleaseValidator: Drupal\automatic_updates_extensions\Validator\UpdateReleaseValidator:
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
Drupal\automatic_updates_extensions\Validator\ForbidCoreChangesValidator:
tags:
- { name: event_subscriber }
Drupal\automatic_updates_extensions\Validator\RequestedUpdateValidator: Drupal\automatic_updates_extensions\Validator\RequestedUpdateValidator:
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
<?php
declare(strict_types=1);
namespace Drupal\automatic_updates_extensions\Validator;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\package_manager\ComposerInspector;
use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\Event\StatusCheckEvent;
use Drupal\package_manager\InstalledPackagesList;
use Drupal\package_manager\PathLocator;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Validates that no changes were made to Drupal Core packages.
*
* @internal
* This is an internal part of Automatic Updates and may be changed or removed
* at any time without warning. External code should not interact with this
* class.
*/
final class ForbidCoreChangesValidator implements EventSubscriberInterface {
use StringTranslationTrait;
public function __construct(
private readonly PathLocator $pathLocator,
private readonly ComposerInspector $composerInspector,
) {}
/**
* Validates the staged packages.
*
* @param \Drupal\package_manager\Event\StatusCheckEvent|\Drupal\package_manager\Event\PreApplyEvent $event
* The event object.
*/
public function validateStagedCorePackages(StatusCheckEvent|PreApplyEvent $event): void {
$stage = $event->stage;
// We only want to do this check if the stage belongs to Automatic Updates
// Extensions.
if ($stage->getType() !== 'automatic_updates_extensions:attended' || !$stage->stageDirectoryExists()) {
return;
}
$active_core_packages = $this->getInstalledCorePackages($this->pathLocator->getProjectRoot());
$stage_core_packages = $this->getInstalledCorePackages($stage->getStageDirectory());
$new_packages = $stage_core_packages->getPackagesNotIn($active_core_packages);
$removed_packages = $active_core_packages->getPackagesNotIn($stage_core_packages);
$changed_packages = $active_core_packages->getPackagesWithDifferentVersionsIn($stage_core_packages);
$error_messages = [];
foreach ($new_packages as $new_package) {
$error_messages[] = $this->t("'@name' installed.", ['@name' => $new_package->name]);
}
foreach ($removed_packages as $removed_package) {
$error_messages[] = $this->t("'@name' removed.", ['@name' => $removed_package->name]);
}
foreach ($changed_packages as $name => $updated_package) {
$error_messages[] = $this->t(
"'@name' version changed from @active_version to @staged_version.",
[
'@name' => $updated_package->name,
'@staged_version' => $stage_core_packages[$name]->version,
'@active_version' => $updated_package->version,
]
);
}
if ($error_messages) {
$event->addError($error_messages, $this->t(
'Updating Drupal Core while updating extensions is currently not supported. Use <a href=":url">this form</a> to update Drupal core. The following changes were made to the Drupal core packages:',
[':url' => Url::fromRoute('update.report_update')->toString()]
));
}
}
/**
* Gets all the installed core packages for a given project root.
*
* This method differs from
* \Drupal\package_manager\ComposerInspector::getInstalledPackagesList in that
* it ensures that the 'drupal/core' is included in the list if present.
*
* @param string $composer_root
* The path to the composer root.
*
* @return \Drupal\package_manager\InstalledPackagesList
* The installed core packages.
*/
private function getInstalledCorePackages(string $composer_root): InstalledPackagesList {
$installed_package_list = $this->composerInspector->getInstalledPackagesList($composer_root);
$core_packages = $installed_package_list->getCorePackages();
if (isset($installed_package_list['drupal/core']) && !isset($core_packages['drupal/core'])) {
$core_packages = new InstalledPackagesList(array_merge($core_packages->getArrayCopy(), ['drupal/core' => $installed_package_list['drupal/core']]));
}
return $core_packages;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
$events[StatusCheckEvent::class][] = ['validateStagedCorePackages'];
$events[PreApplyEvent::class][] = ['validateStagedCorePackages'];
return $events;
}
}
<?php
namespace Drupal\Tests\automatic_updates_extensions\Kernel\Validator;
use Drupal\automatic_updates_extensions\ExtensionUpdateStage;
use Drupal\package_manager\Exception\StageEventException;
use Drupal\package_manager\ValidationResult;
use Drupal\Tests\automatic_updates_extensions\Kernel\AutomaticUpdatesExtensionsKernelTestBase;
/**
* @coversDefaultClass \Drupal\automatic_updates_extensions\Validator\ForbidCoreChangesValidator
* @group automatic_updates_extensions
* @internal
*/
class ForbidCoreChangesValidatorTest extends AutomaticUpdatesExtensionsKernelTestBase {
/**
* Tests error messages if requested updates were not staged.
*
* @param array $staged_versions
* An array of the staged versions where the keys are the package names and
* the values are the package versions or NULL if the package should be
* removed in the stage.
* @param string[][] $new_packages
* An array of the new packages to add to the stage.
* @param ValidationResult[] $expected_results
* The expected validation results.
*
* @dataProvider providerTestErrorMessage
*/
public function testErrorMessages(array $staged_versions, array $new_packages, array $expected_results): void {
$this->setReleaseMetadata([
'semver_test' => __DIR__ . '/../../../fixtures/release-history/semver_test.1.1.xml',
'drupal' => __DIR__ . '/../../../../../package_manager/tests/fixtures/release-history/drupal.9.8.2.xml',
]);
$this->getStageFixtureManipulator()->addPackage([
'name' => 'drupal/non-core',
'version' => '1.0.0',
'type' => 'package',
]);
foreach ($staged_versions as $package => $version) {
if ($version === NULL) {
$this->getStageFixtureManipulator()->removePackage($package);
continue;
}
$this->getStageFixtureManipulator()->setVersion($package, $version);
}
foreach ($new_packages as $package) {
$this->getStageFixtureManipulator()->addPackage($package);
}
$this->getStageFixtureManipulator()->setVersion('drupal/semver_test', '8.1.1');
$stage = $this->container->get(ExtensionUpdateStage::class);
$stage->begin([
'semver_test' => '8.1.1',
]);
$stage->stage();
$this->assertStatusCheckResults($expected_results, $stage);
try {
$stage->apply();
$this->fail('Expecting an exception.');
}
catch (StageEventException $exception) {
$this->assertExpectedResultsFromException($expected_results, $exception);
}
}
/**
* Data provider for testErrorMessage().
*
* @return mixed[]
* The test cases.
*/
public function providerTestErrorMessage(): array {
$summary = t('Updating Drupal Core while updating extensions is currently not supported. Use <a href="/admin/reports/updates/update">this form</a> to update Drupal core. The following changes were made to the Drupal core packages:');
return [
'drupal/core updated, non-core updated' => [
[
'drupal/core' => '9.8.1',
'drupal/non-core' => '1.0.1',
],
[],
[ValidationResult::createError([t("'drupal/core' version changed from 9.8.0 to 9.8.1.")], $summary)],
],
'drupal/core-recommended and drupal/core updated, non-core package installed' => [
[
'drupal/core-recommended' => '9.8.1',
'drupal/core' => '9.8.1',
],
[
[
'name' => 'other-org/other-package',
'type' => 'package',
],
],
[
ValidationResult::createError(
[
t("'drupal/core-recommended' version changed from 9.8.0 to 9.8.1."),
t("'drupal/core' version changed from 9.8.0 to 9.8.1."),
],
$summary
),
],
],
'drupal/core-recommended removed, drupal/core updated, drupal/core-composer-scaffold installed, non-core package removed' => [
[
'drupal/core-recommended' => NULL,
'drupal/core' => '9.8.1',
'drupal/non-core' => NULL,
],
[
[
'name' => 'drupal/core-composer-scaffold',
'type' => 'package',
],
],
[
ValidationResult::createError(
[
t("'drupal/core-composer-scaffold' installed."),
t("'drupal/core-recommended' removed."),
t("'drupal/core' version changed from 9.8.0 to 9.8.1."),
],
$summary
),
],
],
];
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment