Skip to content
Snippets Groups Projects
Commit f10b4d35 authored by Lucas Hedding's avatar Lucas Hedding Committed by Lucas Hedding
Browse files

Issue #3092777 by heddn: Fail in place update if modified files hash is not available

parent 0073f6d4
Branches 2.1.x
Tags 2.1.0
No related merge requests found
...@@ -239,21 +239,21 @@ display: ...@@ -239,21 +239,21 @@ display:
value: value:
6: '6' 6: '6'
group: 1 group: 1
exposed: false exposed: true
expose: expose:
operator_id: '' operator_id: severity_op
label: '' label: 'Severity level'
description: '' description: ''
use_operator: false use_operator: false
operator: '' operator: severity_op
operator_limit_selection: false identifier: severity
operator_list: { }
identifier: ''
required: false required: false
remember: false remember: false
multiple: false multiple: true
remember_roles: remember_roles:
authenticated: authenticated authenticated: authenticated
anonymous: '0'
administrator: '0'
reduce: false reduce: false
is_grouped: false is_grouped: false
group_info: group_info:
...@@ -305,6 +305,7 @@ display: ...@@ -305,6 +305,7 @@ display:
max-age: -1 max-age: -1
contexts: contexts:
- 'languages:language_interface' - 'languages:language_interface'
- url
- url.query_args - url.query_args
- user.permissions - user.permissions
tags: { } tags: { }
...@@ -329,6 +330,7 @@ display: ...@@ -329,6 +330,7 @@ display:
max-age: -1 max-age: -1
contexts: contexts:
- 'languages:language_interface' - 'languages:language_interface'
- url
- url.query_args - url.query_args
- user.permissions - user.permissions
tags: { } tags: { }
...@@ -8,7 +8,7 @@ use Drupal\Core\Messenger\MessengerInterface; ...@@ -8,7 +8,7 @@ use Drupal\Core\Messenger\MessengerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
/** /**
* Returns responses for Test Automatic Updates routes. * Returns responses for Automatic Updates routes.
*/ */
class InPlaceUpdateController extends ControllerBase { class InPlaceUpdateController extends ControllerBase {
...@@ -47,7 +47,7 @@ class InPlaceUpdateController extends ControllerBase { ...@@ -47,7 +47,7 @@ class InPlaceUpdateController extends ControllerBase {
$message = $this->t('Update successful'); $message = $this->t('Update successful');
if (!$updated) { if (!$updated) {
$message_type = MessengerInterface::TYPE_ERROR; $message_type = MessengerInterface::TYPE_ERROR;
$message = $this->t('Update Failed'); $message = $this->t('Update failed. Please review logs to determine the cause.');
} }
$this->messenger()->addMessage($message, $message_type); $this->messenger()->addMessage($message, $message_type);
return $this->redirect('automatic_updates.settings'); return $this->redirect('automatic_updates.settings');
......
...@@ -46,7 +46,7 @@ trait ProjectInfoTrait { ...@@ -46,7 +46,7 @@ trait ProjectInfoTrait {
$info['version'] = $this->getExtensionVersion($info); $info['version'] = $this->getExtensionVersion($info);
}); });
$system = isset($infos['system']) ? $infos['system'] : NULL; $system = isset($infos['system']) ? $infos['system'] : NULL;
$infos = array_filter($infos, function (array $info, $project_name) { $infos = array_filter($infos, static function (array $info, $project_name) {
return $info && $info['project'] === $project_name; return $info && $info['project'] === $project_name;
}, ARRAY_FILTER_USE_BOTH); }, ARRAY_FILTER_USE_BOTH);
if ($system) { if ($system) {
...@@ -70,7 +70,7 @@ trait ProjectInfoTrait { ...@@ -70,7 +70,7 @@ trait ProjectInfoTrait {
return $info['version']; return $info['version'];
} }
// Handle experimental modules from core. // Handle experimental modules from core.
if (substr($info['install path'], 0, 4) === "core") { if (strpos($info['install path'], "core") === 0) {
return $this->getExtensionList('module')->get('system')->info['version']; return $this->getExtensionList('module')->get('system')->info['version'];
} }
\Drupal::logger('automatic_updates')->error('Version cannot be located for @extension', ['@extension' => $extension_name]); \Drupal::logger('automatic_updates')->error('Version cannot be located for @extension', ['@extension' => $extension_name]);
...@@ -98,7 +98,7 @@ trait ProjectInfoTrait { ...@@ -98,7 +98,7 @@ trait ProjectInfoTrait {
$project_name = $this->getSuffix($composer_json['name'], '/', $extension_name); $project_name = $this->getSuffix($composer_json['name'], '/', $extension_name);
} }
} }
if (substr($info['install path'], 0, 4) === "core") { if (strpos($info['install path'], 'core') === 0) {
$project_name = 'drupal'; $project_name = 'drupal';
} }
return $project_name; return $project_name;
......
...@@ -196,15 +196,20 @@ class InPlaceUpdate implements UpdateInterface { ...@@ -196,15 +196,20 @@ class InPlaceUpdate implements UpdateInterface {
* Return TRUE if modified files exist, FALSE otherwise. * Return TRUE if modified files exist, FALSE otherwise.
*/ */
protected function checkModifiedFiles($project_name, $project_type, ArchiverInterface $archive) { protected function checkModifiedFiles($project_name, $project_type, ArchiverInterface $archive) {
$extensions = []; if ($project_type === 'core') {
foreach (['module', 'profile', 'theme'] as $extension_type) { $project_type = 'module';
$extensions[] = $this->getInfos($extension_type);
} }
$extensions = array_merge(...$extensions); $extensions = $this->getInfos($project_type);
/** @var \Drupal\automatic_updates\Services\ModifiedFilesInterface $modified_files */ /** @var \Drupal\automatic_updates\Services\ModifiedFilesInterface $modified_files */
$modified_files = \Drupal::service('automatic_updates.modified_files'); $modified_files = \Drupal::service('automatic_updates.modified_files');
$files = iterator_to_array($modified_files->getModifiedFiles([$extensions[$project_name]])); try {
$files = iterator_to_array($modified_files->getModifiedFiles([$extensions[$project_name]], TRUE));
}
catch (RequestException $exception) {
// While not strictly true that there are modified files, we can't be sure
// there aren't any. So assume the worst.
return TRUE;
}
$files = array_unique($files); $files = array_unique($files);
$archive_files = $archive->listContents(); $archive_files = $archive->listContents();
foreach ($archive_files as $index => &$archive_file) { foreach ($archive_files as $index => &$archive_file) {
...@@ -247,8 +252,9 @@ class InPlaceUpdate implements UpdateInterface { ...@@ -247,8 +252,9 @@ class InPlaceUpdate implements UpdateInterface {
]); ]);
} }
catch (RequestException $exception) { catch (RequestException $exception) {
if ($exception->getResponse()->getStatusCode() === 429 && ($retry = $exception->getResponse()->getHeader('Retry-After'))) { $response = $exception->getResponse();
$this->doGetArchive($url, $destination, $retry[0] * 1000); if (!$response || ($response->getStatusCode() === 429 && ($retry = $response->getHeader('Retry-After')))) {
$this->doGetArchive($url, $destination, $retry[0] ?? 10 * 1000);
} }
else { else {
$this->logger->error('Retrieval of "@url" failed with: @message', [ $this->logger->error('Retrieval of "@url" failed with: @message', [
...@@ -371,7 +377,7 @@ class InPlaceUpdate implements UpdateInterface { ...@@ -371,7 +377,7 @@ class InPlaceUpdate implements UpdateInterface {
* TRUE if path was removed, else FALSE. * TRUE if path was removed, else FALSE.
*/ */
protected function stripFileDirectoryPath(&$file) { protected function stripFileDirectoryPath(&$file) {
if (substr($file, 0, 6) === self::ARCHIVE_DIRECTORY) { if (strpos($file, self::ARCHIVE_DIRECTORY) === 0) {
$file = substr($file, 6); $file = substr($file, 6);
return TRUE; return TRUE;
} }
...@@ -440,11 +446,11 @@ class InPlaceUpdate implements UpdateInterface { ...@@ -440,11 +446,11 @@ class InPlaceUpdate implements UpdateInterface {
* The iterator of SplFileInfos. * The iterator of SplFileInfos.
*/ */
protected function getFilesList($directory) { protected function getFilesList($directory) {
$filter = function ($file, $file_name, $iterator) { $filter = static function ($file, $file_name, $iterator) {
/** @var \SplFileInfo $file */ /** @var \SplFileInfo $file */
/** @var string $file_name */ /** @var string $file_name */
/** @var \RecursiveDirectoryIterator $iterator */ /** @var \RecursiveDirectoryIterator $iterator */
if ($iterator->hasChildren() && !in_array($file->getFilename(), ['.git'], TRUE)) { if ($iterator->hasChildren() && $file->getFilename() !== '.git') {
return TRUE; return TRUE;
} }
$skipped_files = [ $skipped_files = [
...@@ -471,7 +477,7 @@ class InPlaceUpdate implements UpdateInterface { ...@@ -471,7 +477,7 @@ class InPlaceUpdate implements UpdateInterface {
*/ */
protected function buildUrl($project_name, $file_name) { protected function buildUrl($project_name, $file_name) {
$uri = $this->configFactory->get('automatic_updates.settings')->get('download_uri'); $uri = $this->configFactory->get('automatic_updates.settings')->get('download_uri');
return Url::fromUri($uri . "/$project_name/" . $file_name)->toString(); return Url::fromUri("$uri/$project_name/$file_name")->toString();
} }
/** /**
......
...@@ -10,6 +10,7 @@ use Drupal\Signify\ChecksumList; ...@@ -10,6 +10,7 @@ use Drupal\Signify\ChecksumList;
use Drupal\Signify\FailedCheckumFilter; use Drupal\Signify\FailedCheckumFilter;
use Drupal\Signify\Verifier; use Drupal\Signify\Verifier;
use GuzzleHttp\ClientInterface; use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise\EachPromise; use GuzzleHttp\Promise\EachPromise;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
...@@ -43,7 +44,7 @@ class ModifiedFiles implements ModifiedFilesInterface { ...@@ -43,7 +44,7 @@ class ModifiedFiles implements ModifiedFilesInterface {
protected $configFactory; protected $configFactory;
/** /**
* ModifiedCode constructor. * ModifiedFiles constructor.
* *
* @param \Psr\Log\LoggerInterface $logger * @param \Psr\Log\LoggerInterface $logger
* The logger. * The logger.
...@@ -63,16 +64,19 @@ class ModifiedFiles implements ModifiedFilesInterface { ...@@ -63,16 +64,19 @@ class ModifiedFiles implements ModifiedFilesInterface {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getModifiedFiles(array $extensions = []) { public function getModifiedFiles(array $extensions = [], $exception_on_failure = FALSE) {
$modified_files = new \ArrayIterator(); $modified_files = new \ArrayIterator();
/** @var \GuzzleHttp\Promise\PromiseInterface[] $promises */ /** @var \GuzzleHttp\Promise\PromiseInterface[] $promises */
$promises = $this->getHashRequests($extensions); $promises = $this->getHashRequests($extensions);
// Wait until all the requests are finished. // Wait until all the requests are finished.
(new EachPromise($promises, [ (new EachPromise($promises, [
'concurrency' => 4, 'concurrency' => 4,
'fulfilled' => function ($resource) use ($modified_files) { 'fulfilled' => function (array $resource) use ($modified_files) {
$this->processHashes($resource, $modified_files); $this->processHashes($resource, $modified_files);
}, },
'rejected' => function (RequestException $exception) use ($exception_on_failure) {
$this->processFailures($exception, $exception_on_failure);
},
]))->promise()->wait(); ]))->promise()->wait();
return $modified_files; return $modified_files;
} }
...@@ -80,14 +84,16 @@ class ModifiedFiles implements ModifiedFilesInterface { ...@@ -80,14 +84,16 @@ class ModifiedFiles implements ModifiedFilesInterface {
/** /**
* Process checking hashes of files from external URL. * Process checking hashes of files from external URL.
* *
* @param array $resource * @param array $hash
* An array of http response and project info. * An array of http response and project info.
* @param \ArrayIterator $modified_files * @param \ArrayIterator $modified_files
* The list of modified files. * The list of modified files.
*
* @throws \SodiumException
*/ */
protected function processHashes(array $resource, \ArrayIterator $modified_files) { protected function processHashes(array $hash, \ArrayIterator $modified_files) {
$contents = $resource['contents']; $contents = $hash['contents'];
$info = $resource['info']; $info = $hash['info'];
$directory_root = $info['install path']; $directory_root = $info['install path'];
if ($info['project'] === 'drupal') { if ($info['project'] === 'drupal') {
$directory_root = ''; $directory_root = '';
...@@ -113,6 +119,21 @@ class ModifiedFiles implements ModifiedFilesInterface { ...@@ -113,6 +119,21 @@ class ModifiedFiles implements ModifiedFilesInterface {
} }
} }
/**
* Handle HTTP failures.
*
* @param \GuzzleHttp\Exception\RequestException $exception
* The request exception.
* @param bool $exception_on_failure
* Throw exception on HTTP failures, defaults to FALSE.
*/
protected function processFailures(RequestException $exception, $exception_on_failure) {
if ($exception_on_failure) {
watchdog_exception('automatic_updates', $exception);
throw $exception;
}
}
/** /**
* Get an iterator of promises that return a resource stream. * Get an iterator of promises that return a resource stream.
* *
...@@ -151,13 +172,14 @@ class ModifiedFiles implements ModifiedFilesInterface { ...@@ -151,13 +172,14 @@ class ModifiedFiles implements ModifiedFilesInterface {
return $this->httpClient->requestAsync('GET', $url, [ return $this->httpClient->requestAsync('GET', $url, [
'stream' => TRUE, 'stream' => TRUE,
'read_timeout' => 30, 'read_timeout' => 30,
]) ])->then(
->then(function (ResponseInterface $response) use ($info) { static function (ResponseInterface $response) use ($info) {
return [ return [
'contents' => $response->getBody()->getContents(), 'contents' => $response->getBody()->getContents(),
'info' => $info, 'info' => $info,
]; ];
}); }
);
} }
/** /**
...@@ -174,7 +196,7 @@ class ModifiedFiles implements ModifiedFilesInterface { ...@@ -174,7 +196,7 @@ class ModifiedFiles implements ModifiedFilesInterface {
$project_name = $info['project']; $project_name = $info['project'];
$hash_name = $this->getHashName($info); $hash_name = $this->getHashName($info);
$uri = ltrim($this->configFactory->get('automatic_updates.settings')->get('hashes_uri'), '/'); $uri = ltrim($this->configFactory->get('automatic_updates.settings')->get('hashes_uri'), '/');
return Url::fromUri($uri . "/$project_name/$version/$hash_name")->toString(); return Url::fromUri("$uri/$project_name/$version/$hash_name")->toString();
} }
/** /**
......
...@@ -13,10 +13,12 @@ interface ModifiedFilesInterface { ...@@ -13,10 +13,12 @@ interface ModifiedFilesInterface {
* @param array $extensions * @param array $extensions
* The list of extensions, keyed by extension name with values an info * The list of extensions, keyed by extension name with values an info
* array. * array.
* @param bool $exception_on_failure
* (optional) Throw exception on HTTP failures, defaults to FALSE.
* *
* @return \Iterator * @return \Iterator
* The modified files. * The modified files.
*/ */
public function getModifiedFiles(array $extensions = []); public function getModifiedFiles(array $extensions = [], $exception_on_failure = FALSE);
} }
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