Skip to content
Snippets Groups Projects
Commit acee8d4e authored by Ted Bowman's avatar Ted Bowman Committed by Adam G-H
Browse files

Issue #3230235 by tedbow, phenaproxima: Validate that an update is supported before it begins

parent 4fd5ec3e
No related branches found
No related tags found
1 merge request!24Issue #3230235: Validate that an update is supported before an update begins and after staged
...@@ -76,6 +76,11 @@ services: ...@@ -76,6 +76,11 @@ services:
arguments: ['%app.root%', '%site.path%', '@file_system', '@stream_wrapper_manager'] arguments: ['%app.root%', '%site.path%', '@file_system', '@stream_wrapper_manager']
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
automatic_updates.update_version_subscriber:
class: Drupal\automatic_updates\Event\UpdateVersionSubscriber
arguments: ['@module_handler']
tags:
- { name: event_subscriber }
automatic_updates.composer_executable_validator: automatic_updates.composer_executable_validator:
class: Drupal\automatic_updates\Validation\ComposerExecutableValidator class: Drupal\automatic_updates\Validation\ComposerExecutableValidator
arguments: ['@automatic_updates.exec_finder'] arguments: ['@automatic_updates.exec_finder']
......
...@@ -9,4 +9,32 @@ class PreStartEvent extends UpdateEvent { ...@@ -9,4 +9,32 @@ class PreStartEvent extends UpdateEvent {
use ExcludedPathsTrait; use ExcludedPathsTrait;
/**
* The desired package versions to update to, keyed by package name.
*
* @var string[]
*/
protected $packageVersions;
/**
* Constructs a PreStartEvent.
*
* @param string[] $package_versions
* (optional) The desired package versions to update to, keyed by package
* name.
*/
public function __construct(array $package_versions = []) {
$this->packageVersions = $package_versions;
}
/**
* Returns the desired package versions to update to.
*
* @return string[]
* The desired package versions to update to, keyed by package name.
*/
public function getPackageVersions(): array {
return $this->packageVersions;
}
} }
<?php
namespace Drupal\automatic_updates\Event;
use Drupal\automatic_updates\AutomaticUpdatesEvents;
use Drupal\automatic_updates\Validation\ValidationResult;
use Drupal\Core\Extension\ExtensionVersion;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Validates that core updates are within a supported version range.
*/
class UpdateVersionSubscriber implements EventSubscriberInterface {
use StringTranslationTrait;
/**
* Constructs an UpdateVersionSubscriber.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
*/
public function __construct(ModuleHandlerInterface $module_handler) {
// Load procedural functions needed for ::getCoreVersion().
$module_handler->loadInclude('update', 'inc', 'update.compare');
}
/**
* Returns the running core version, according to the Update module.
*
* @return string
* The running core version as known to the Update module.
*/
protected function getCoreVersion(): string {
$available_updates = update_calculate_project_data(update_get_available());
return $available_updates['drupal']['existing_version'];
}
/**
* Validates that core is not being updated to another minor or major version.
*
* @param \Drupal\automatic_updates\Event\PreStartEvent $event
* The event object.
*/
public function checkUpdateVersion(PreStartEvent $event): void {
$from_version = ExtensionVersion::createFromVersionString($this->getCoreVersion());
$to_version = ExtensionVersion::createFromVersionString($event->getPackageVersions()['drupal/core']);
if ($from_version->getMajorVersion() !== $to_version->getMajorVersion()) {
$error = ValidationResult::createError([
$this->t('Updating from one major version to another is not supported.'),
]);
$event->addValidationResult($error);
}
elseif ($from_version->getMinorVersion() !== $to_version->getMinorVersion()) {
$error = ValidationResult::createError([
$this->t('Updating from one minor version to another is not supported.'),
]);
$event->addValidationResult($error);
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return [
AutomaticUpdatesEvents::PRE_START => 'checkUpdateVersion',
];
}
}
...@@ -166,10 +166,12 @@ class Updater { ...@@ -166,10 +166,12 @@ class Updater {
if (count($project_versions) !== 1 || !array_key_exists('drupal', $project_versions)) { if (count($project_versions) !== 1 || !array_key_exists('drupal', $project_versions)) {
throw new \InvalidArgumentException("Currently only updates to Drupal core are supported."); throw new \InvalidArgumentException("Currently only updates to Drupal core are supported.");
} }
$packages[] = 'drupal/core:' . $project_versions['drupal']; $packages = [
'drupal/core' => $project_versions['drupal'],
];
$stage_key = $this->createActiveStage($packages); $stage_key = $this->createActiveStage($packages);
/** @var \Drupal\automatic_updates\Event\PreStartEvent $event */ /** @var \Drupal\automatic_updates\Event\PreStartEvent $event */
$event = $this->dispatchUpdateEvent(new PreStartEvent(), AutomaticUpdatesEvents::PRE_START); $event = $this->dispatchUpdateEvent(new PreStartEvent($packages), AutomaticUpdatesEvents::PRE_START);
$this->beginner->begin(static::getActiveDirectory(), static::getStageDirectory(), $this->getExclusions($event)); $this->beginner->begin(static::getActiveDirectory(), static::getStageDirectory(), $this->getExclusions($event));
return $stage_key; return $stage_key;
} }
...@@ -216,9 +218,6 @@ class Updater { ...@@ -216,9 +218,6 @@ class Updater {
public function commit(): void { public function commit(): void {
/** @var \Drupal\automatic_updates\Event\PreCommitEvent $event */ /** @var \Drupal\automatic_updates\Event\PreCommitEvent $event */
$event = $this->dispatchUpdateEvent(new PreCommitEvent(), AutomaticUpdatesEvents::PRE_COMMIT); $event = $this->dispatchUpdateEvent(new PreCommitEvent(), AutomaticUpdatesEvents::PRE_COMMIT);
// @todo Pass excluded paths into the committer once
// https://github.com/php-tuf/composer-stager/pull/14 is in a tagged release
// of Composer Stager.
$this->committer->commit($this->getStageDirectory(), static::getActiveDirectory(), $this->getExclusions($event)); $this->committer->commit($this->getStageDirectory(), static::getActiveDirectory(), $this->getExclusions($event));
} }
...@@ -255,12 +254,17 @@ class Updater { ...@@ -255,12 +254,17 @@ class Updater {
* The active update ID. * The active update ID.
*/ */
private function createActiveStage(array $package_versions): string { private function createActiveStage(array $package_versions): string {
$requirements = [];
foreach ($package_versions as $package_name => $version) {
$requirements[] = "$package_name:$version";
}
$value = static::STATE_KEY . microtime(); $value = static::STATE_KEY . microtime();
$this->state->set( $this->state->set(
static::STATE_KEY, static::STATE_KEY,
[ [
'id' => $value, 'id' => $value,
'package_versions' => $package_versions, 'package_versions' => $requirements,
] ]
); );
return $value; return $value;
......
...@@ -15,7 +15,7 @@ class ExclusionsTest extends BrowserTestBase { ...@@ -15,7 +15,7 @@ class ExclusionsTest extends BrowserTestBase {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected static $modules = ['automatic_updates_test']; protected static $modules = ['automatic_updates_test', 'update_test'];
/** /**
* {@inheritdoc} * {@inheritdoc}
...@@ -40,6 +40,22 @@ class ExclusionsTest extends BrowserTestBase { ...@@ -40,6 +40,22 @@ class ExclusionsTest extends BrowserTestBase {
$settings['file_private_path'] = 'files/private'; $settings['file_private_path'] = 'files/private';
new Settings($settings); new Settings($settings);
// Updater::begin() will trigger update validators, such as
// \Drupal\automatic_updates\Event\UpdateVersionSubscriber, that need to
// fetch release metadata. We need to ensure that those HTTP request(s)
// succeed, so set them up to point to our fake release metadata.
$this->config('update_test.settings')
->set('xml_map', [
'drupal' => '0.0',
])
->save();
$this->config('update.settings')
->set('fetch.url', $this->baseUrl . '/automatic-update-test')
->save();
$this->config('update_test.settings')
->set('system_info.#all.version', '9.8.0')
->save();
$updater->begin(['drupal' => '9.8.1']); $updater->begin(['drupal' => '9.8.1']);
$this->assertFileDoesNotExist("$stage_dir/sites/default/settings.php"); $this->assertFileDoesNotExist("$stage_dir/sites/default/settings.php");
$this->assertFileDoesNotExist("$stage_dir/sites/default/settings.local.php"); $this->assertFileDoesNotExist("$stage_dir/sites/default/settings.local.php");
......
...@@ -3,6 +3,10 @@ ...@@ -3,6 +3,10 @@
namespace Drupal\Tests\automatic_updates\Kernel; namespace Drupal\Tests\automatic_updates\Kernel;
use Drupal\KernelTests\KernelTestBase; use Drupal\KernelTests\KernelTestBase;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\Utils;
use Prophecy\Argument; use Prophecy\Argument;
/** /**
...@@ -17,14 +21,28 @@ class UpdaterTest extends KernelTestBase { ...@@ -17,14 +21,28 @@ class UpdaterTest extends KernelTestBase {
*/ */
protected static $modules = [ protected static $modules = [
'automatic_updates', 'automatic_updates',
'update', 'automatic_updates_test',
'composer_stager_bypass', 'composer_stager_bypass',
'update',
'update_test',
]; ];
/** /**
* Tests that correct versions are staged after calling ::begin(). * Tests that correct versions are staged after calling ::begin().
*/ */
public function testCorrectVersionsStaged() { public function testCorrectVersionsStaged() {
// Ensure that the HTTP client will fetch our fake release metadata.
$release_data = Utils::tryFopen(__DIR__ . '/../../fixtures/release-history/drupal.0.0.xml', 'r');
$response = new Response(200, [], Utils::streamFor($release_data));
$handler = new MockHandler([$response]);
$client = new Client(['handler' => $handler]);
$this->container->set('http_client', $client);
// Set the running core version to 9.8.0.
$this->config('update_test.settings')
->set('system_info.#all.version', '9.8.0')
->save();
$this->container->get('automatic_updates.updater')->begin([ $this->container->get('automatic_updates.updater')->begin([
'drupal' => '9.8.1', 'drupal' => '9.8.1',
]); ]);
......
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