Skip to content
Snippets Groups Projects
Commit 438237fd authored by Kunal Sachdev's avatar Kunal Sachdev Committed by Adam G-H
Browse files

Issue #3305564 by omkar.podey, kunal.sachdev, phenaproxima: Create a validator...

Issue #3305564 by omkar.podey, kunal.sachdev, phenaproxima: Create a validator to stop newly installed packages from overwriting existing directories on apply
parent 7bab2994
No related branches found
No related tags found
No related merge requests found
Showing
with 277 additions and 1 deletion
...@@ -22,6 +22,10 @@ class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensi ...@@ -22,6 +22,10 @@ class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensi
// In this test, we don't care whether the updated projects are secure and // In this test, we don't care whether the updated projects are secure and
// supported. // supported.
$this->disableValidators[] = 'automatic_updates_extensions.validator.target_release'; $this->disableValidators[] = 'automatic_updates_extensions.validator.target_release';
// @todo The validator being tested covers the same cases as the following
// validator. PackagesInstalledWithComposerValidatorTest will be removed
// in https://drupal.org/i/3303900.
$this->disableValidators[] = 'package_manager.validator.overwrite_existing_packages';
parent::setUp(); parent::setUp();
} }
......
...@@ -111,6 +111,12 @@ services: ...@@ -111,6 +111,12 @@ services:
- '@package_manager.path_locator' - '@package_manager.path_locator'
tags: tags:
- { name: event_subscriber } - { name: event_subscriber }
package_manager.validator.overwrite_existing_packages:
class: Drupal\package_manager\Validator\OverwriteExistingPackagesValidator
arguments:
- '@package_manager.path_locator'
tags:
- { name: event_subscriber }
package_manager.test_site_excluder: package_manager.test_site_excluder:
class: Drupal\package_manager\PathExcluder\TestSiteExcluder class: Drupal\package_manager\PathExcluder\TestSiteExcluder
arguments: arguments:
......
...@@ -197,7 +197,7 @@ class ComposerUtility { ...@@ -197,7 +197,7 @@ class ComposerUtility {
* The installed package data as represented in Composer's `installed.php`, * The installed package data as represented in Composer's `installed.php`,
* keyed by package name. * keyed by package name.
*/ */
private function getInstalledPackagesData(): array { public function getInstalledPackagesData(): array {
$installed_php = implode(DIRECTORY_SEPARATOR, [ $installed_php = implode(DIRECTORY_SEPARATOR, [
// Composer returns the absolute path to the vendor directory by default. // Composer returns the absolute path to the vendor directory by default.
$this->getComposer()->getConfig()->get('vendor-dir'), $this->getComposer()->getConfig()->get('vendor-dir'),
......
<?php
namespace Drupal\package_manager\Validator;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\Event\PreOperationStageEvent;
use Drupal\package_manager\PathLocator;
/**
* Validates that newly installed packages don't overwrite existing directories.
*
* @internal
* This is an internal part of Package Manager and may be changed or removed
* at any time without warning. External code should not interact with this
* class.
*/
final class OverwriteExistingPackagesValidator implements PreOperationStageValidatorInterface {
use StringTranslationTrait;
/**
* The path locator service.
*
* @var \Drupal\package_manager\PathLocator
*/
protected $pathLocator;
/**
* Constructs a OverwriteExistingPackagesValidator object.
*
* @param \Drupal\package_manager\PathLocator $path_locator
* The path locator service.
*/
public function __construct(PathLocator $path_locator) {
$this->pathLocator = $path_locator;
}
/**
* Validates that new installed packages don't overwrite existing directories.
*/
public function validateStagePreOperation(PreOperationStageEvent $event): void {
$stage = $event->getStage();
$active_composer = $stage->getActiveComposer();
$stage_composer = $stage->getStageComposer();
$active_dir = $this->pathLocator->getProjectRoot();
$stage_dir = $stage->getStageDirectory();
$new_packages = $stage_composer->getPackagesNotIn($active_composer);
$installed_packages_data = $stage_composer->getInstalledPackagesData();
// Although unlikely, it is possible that package data could be missing for
// some new packages.
$missing_new_packages = array_diff_key($new_packages, $installed_packages_data);
if ($missing_new_packages) {
$missing_new_packages = array_keys($missing_new_packages);
$event->addError($missing_new_packages, $this->t('Package Manager could not get the data for the following packages:'));
return;
}
$new_installed_data = array_intersect_key($installed_packages_data, $new_packages);
foreach ($new_installed_data as $package_name => $data) {
$relative_path = str_replace($stage_dir, '', $data['install_path']);
if (is_dir($active_dir . DIRECTORY_SEPARATOR . $relative_path)) {
$event->addError([
$this->t('The new package @package will be installed in the directory @path, which already exists but is not managed by Composer.', [
'@package' => $package_name,
'@path' => $relative_path,
]),
]);
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return [
PreApplyEvent::class => 'validateStagePreOperation',
];
}
}
<?php
/**
* @file
*/
// Composer Utility needs the versions key to be present.
return [
'versions' => [],
];
{
"_readme": [
"See \\Drupal\\Tests\\package_manager\\Kernel\\OverwriteExistingPackagesValidatorTest::testNewPackagesOverwriteExisting()."
],
"packages": []
}
<?php
/**
* @file
* Simulates that no packages are installed by Composer.
*
* @see \Drupal\Tests\package_manager\Kernel\OverwriteExistingPackagesValidatorTest::testNewPackagesOverwriteExisting()
*/
return [
'versions' => [],
];
{
"_readme": [
"See \\Drupal\\Tests\\package_manager\\Kernel\\OverwriteExistingPackagesValidatorTest::testNewPackagesOverwriteExisting()."
],
"packages": [
{
"name": "drupal/module_1",
"version": "1.3.0",
"type": "drupal-module"
},
{
"name": "drupal/module_2",
"version": "1.3.0",
"type": "drupal-module"
},
{
"name": "drupal/module_3",
"version": "1.3.0",
"type": "drupal-module"
},
{
"name": "drupal/module_4",
"version": "1.3.0",
"type": "drupal-module"
},
{
"name": "drupal/module_5",
"version": "1.3.0",
"type": "drupal-module"
}
]
}
<?php
/**
* @file
* Simulates several modules installed by Composer.
*
* @see \Drupal\Tests\package_manager\Kernel\OverwriteExistingPackagesValidatorTest::testNewPackagesOverwriteExisting()
*/
$modules_dir = __DIR__ . '/../../modules';
return [
'versions' => [
'drupal/module_1' => [
'type' => 'drupal-module',
'install_path' => $modules_dir . '/module_1',
],
'drupal/module_2' => [
'type' => 'drupal-module',
'install_path' => $modules_dir . '/module_2',
],
'drupal/module_3' => [
'type' => 'drupal-module',
'install_path' => $modules_dir . '/module_3',
],
'drupal/module_4' => [
'type' => 'drupal-module',
'install_path' => $modules_dir . '/module_1',
],
'drupal/module_5' => [
'type' => 'drupal-module',
'install_path' => $modules_dir . '/module_5_different_path',
],
],
];
<?php
namespace Drupal\Tests\package_manager\Kernel;
use Drupal\package_manager\Exception\StageValidationException;
use Drupal\package_manager\ValidationResult;
/**
* @covers \Drupal\package_manager\Validator\OverwriteExistingPackagesValidator
*
* @group package_manager
*/
class OverwriteExistingPackagesValidatorTest extends PackageManagerKernelTestBase {
/**
* Tests that new installed packages overwrite existing directories.
*
* The fixture simulates a scenario where the active directory has three
* modules installed: module_1, module_2, and module_5. None of them are
* managed by Composer.
*
* The staging area has four modules: module_1, module_2, module_3, and
* module_5_different_path. All of them are managed by Composer. We expect the
* following outcomes:
*
* - module_1 and module_2 will raise errors because they would overwrite
* non-Composer managed paths in the active directory.
* - module_3 will cause no problems, since it doesn't exist in the active
* directory at all.
* - module_4, which is defined only in the staged installed.json and
* installed.php, will cause an error because its path collides with
* module_1.
* - module_5_different_path will not cause a problem, even though its package
* name is drupal/module_5, because its project name and path in the staging
* area differ from the active directory.
*/
public function testNewPackagesOverwriteExisting(): void {
$fixtures_dir = __DIR__ . '/../../fixtures/overwrite_existing_validation';
$this->copyFixtureFolderToActiveDirectory("$fixtures_dir/active");
$this->copyFixtureFolderToStageDirectoryOnApply("$fixtures_dir/staged");
$stage = $this->createStage();
$stage->create();
$stage->require(['drupal' => '9.8.1']);
$expected_results = [
ValidationResult::createError([
'The new package drupal/module_1 will be installed in the directory /vendor/composer/../../modules/module_1, which already exists but is not managed by Composer.',
]),
ValidationResult::createError([
'The new package drupal/module_2 will be installed in the directory /vendor/composer/../../modules/module_2, which already exists but is not managed by Composer.',
]),
ValidationResult::createError([
'The new package drupal/module_4 will be installed in the directory /vendor/composer/../../modules/module_1, which already exists but is not managed by Composer.',
]),
];
try {
$stage->apply();
// If no exception occurs, ensure we weren't expecting any errors.
$this->assertEmpty($expected_results);
}
catch (StageValidationException $e) {
$this->assertNotEmpty($expected_results);
$this->assertValidationResultsEqual($expected_results, $e->getResults());
}
}
}
<?php
/**
* @file
* Simulates that no packages are installed.
*/
return [
'versions' => [],
];
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