-
Ted Bowman authoredTed Bowman authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
DiskSpaceValidator.php 4.97 KiB
<?php
namespace Drupal\package_manager\EventSubscriber;
use Drupal\package_manager\Event\PreCreateEvent;
use Drupal\package_manager\Event\PreOperationStageEvent;
use Drupal\Component\FileSystem\FileSystem;
use Drupal\Component\Utility\Bytes;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\package_manager\PathLocator;
/**
* Validates that there is enough free disk space to do staging operations.
*/
class DiskSpaceValidator implements PreOperationStageValidatorInterface {
use StringTranslationTrait;
/**
* The path locator service.
*
* @var \Drupal\package_manager\PathLocator
*/
protected $pathLocator;
/**
* Constructs a DiskSpaceValidator object.
*
* @param \Drupal\package_manager\PathLocator $path_locator
* The path locator service.
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation
* The translation service.
*/
public function __construct(PathLocator $path_locator, TranslationInterface $translation) {
$this->pathLocator = $path_locator;
$this->setStringTranslation($translation);
}
/**
* Wrapper around the disk_free_space() function.
*
* @param string $path
* The path for which to retrieve the amount of free disk space.
*
* @return float
* The number of bytes of free space on the disk.
*
* @throws \RuntimeException
* If the amount of free space could not be determined.
*/
protected function freeSpace(string $path): float {
$free_space = disk_free_space($path);
if ($free_space === FALSE) {
throw new \RuntimeException("Cannot get disk information for $path.");
}
return $free_space;
}
/**
* Wrapper around the stat() function.
*
* @param string $path
* The path to check.
*
* @return array
* The statistics for the path.
*
* @throws \RuntimeException
* If the statistics could not be determined.
*/
protected function stat(string $path): array {
$stat = stat($path);
if ($stat === FALSE) {
throw new \RuntimeException("Cannot get information for $path.");
}
return $stat;
}
/**
* Checks if two paths are located on the same logical disk.
*
* @param string $root
* The path of the project root.
* @param string $vendor
* The path of the vendor directory.
*
* @return bool
* TRUE if the project root and vendor directory are on the same logical
* disk, FALSE otherwise.
*/
protected function areSameLogicalDisk(string $root, string $vendor): bool {
$root_statistics = $this->stat($root);
$vendor_statistics = $this->stat($vendor);
return $root_statistics['dev'] === $vendor_statistics['dev'];
}
/**
* {@inheritdoc}
*/
public function validateStagePreOperation(PreOperationStageEvent $event): void {
$root_path = $this->pathLocator->getProjectRoot();
$vendor_path = $this->pathLocator->getVendorDirectory();
$messages = [];
// @todo Make this configurable or set to a different value in
// https://www.drupal.org/i/3166416.
$minimum_mb = 1024;
$minimum_bytes = Bytes::toNumber($minimum_mb . 'M');
if (!$this->areSameLogicalDisk($root_path, $vendor_path)) {
if ($this->freeSpace($root_path) < $minimum_bytes) {
$messages[] = $this->t('Drupal root filesystem "@root" has insufficient space. There must be at least @space megabytes free.', [
'@root' => $root_path,
'@space' => $minimum_mb,
]);
}
if (is_dir($vendor_path) && $this->freeSpace($vendor_path) < $minimum_bytes) {
$messages[] = $this->t('Vendor filesystem "@vendor" has insufficient space. There must be at least @space megabytes free.', [
'@vendor' => $vendor_path,
'@space' => $minimum_mb,
]);
}
}
elseif ($this->freeSpace($root_path) < $minimum_bytes) {
$messages[] = $this->t('Drupal root filesystem "@root" has insufficient space. There must be at least @space megabytes free.', [
'@root' => $root_path,
'@space' => $minimum_mb,
]);
}
$temp = $this->temporaryDirectory();
if ($this->freeSpace($temp) < $minimum_bytes) {
$messages[] = $this->t('Directory "@temp" has insufficient space. There must be at least @space megabytes free.', [
'@temp' => $temp,
'@space' => $minimum_mb,
]);
}
if ($messages) {
$summary = count($messages) > 1
? $this->t("There is not enough disk space to create a staging area.")
: NULL;
$event->addError($messages, $summary);
}
}
/**
* Returns the path of the system temporary directory.
*
* @return string
* The absolute path of the system temporary directory.
*/
protected function temporaryDirectory(): string {
return FileSystem::getOsTemporaryDirectory();
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return [
PreCreateEvent::class => 'validateStagePreOperation',
];
}
}