1
0
Fork 0

Add a readonly mode to the cache, fixes #9150

pull/9152/head
Jordi Boggiano 2020-08-25 13:55:32 +02:00
parent 875a4784ed
commit 90332f1dbd
No known key found for this signature in database
GPG Key ID: 7BBD42C429EC80BC
13 changed files with 51 additions and 10 deletions

View File

@ -204,6 +204,10 @@ downloads. When the garbage collection is periodically ran, this is the maximum
size the cache will be able to use. Older (less used) files will be removed size the cache will be able to use. Older (less used) files will be removed
first until the cache fits. first until the cache fits.
## cache-read-only
Defaults to `false`. Whether to use the Composer cache in read-only mode.
## bin-compat ## bin-compat
Defaults to `auto`. Determines the compatibility of the binaries to be installed. Defaults to `auto`. Determines the compatibility of the binaries to be installed.

View File

@ -237,6 +237,10 @@
"type": ["string", "integer"], "type": ["string", "integer"],
"description": "The cache max size for the files cache, defaults to \"300MiB\"." "description": "The cache max size for the files cache, defaults to \"300MiB\"."
}, },
"cache-read-only": {
"type": ["boolean"],
"description": "Whether to use the Composer cache in read-only mode."
},
"bin-compat": { "bin-compat": {
"enum": ["auto", "full"], "enum": ["auto", "full"],
"description": "The compatibility of the binaries, defaults to \"auto\" (automatically guessed) and can be \"full\" (compatible with both Windows and Unix-based systems)." "description": "The compatibility of the binaries, defaults to \"auto\" (automatically guessed) and can be \"full\" (compatible with both Windows and Unix-based systems)."

View File

@ -30,19 +30,22 @@ class Cache
private $enabled = true; private $enabled = true;
private $allowlist; private $allowlist;
private $filesystem; private $filesystem;
private $readOnly;
/** /**
* @param IOInterface $io * @param IOInterface $io
* @param string $cacheDir location of the cache * @param string $cacheDir location of the cache
* @param string $allowlist List of characters that are allowed in path names (used in a regex character class) * @param string $allowlist List of characters that are allowed in path names (used in a regex character class)
* @param Filesystem $filesystem optional filesystem instance * @param Filesystem $filesystem optional filesystem instance
* @param bool $readOnly whether the cache is in readOnly mode
*/ */
public function __construct(IOInterface $io, $cacheDir, $allowlist = 'a-z0-9.', Filesystem $filesystem = null) public function __construct(IOInterface $io, $cacheDir, $allowlist = 'a-z0-9.', Filesystem $filesystem = null, $readOnly = false)
{ {
$this->io = $io; $this->io = $io;
$this->root = rtrim($cacheDir, '/\\') . '/'; $this->root = rtrim($cacheDir, '/\\') . '/';
$this->allowlist = $allowlist; $this->allowlist = $allowlist;
$this->filesystem = $filesystem ?: new Filesystem(); $this->filesystem = $filesystem ?: new Filesystem();
$this->readOnly = (bool) $readOnly;
if (!self::isUsable($cacheDir)) { if (!self::isUsable($cacheDir)) {
$this->enabled = false; $this->enabled = false;
@ -59,6 +62,22 @@ class Cache
} }
} }
/**
* @param bool $readOnly
*/
public function setReadOnly($readOnly)
{
$this->readOnly = (bool) $readOnly;
}
/**
* @return bool
*/
public function isReadOnly()
{
return $this->readOnly;
}
public static function isUsable($path) public static function isUsable($path)
{ {
return !preg_match('{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}', $path); return !preg_match('{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}', $path);
@ -90,7 +109,7 @@ class Cache
public function write($file, $contents) public function write($file, $contents)
{ {
if ($this->enabled) { if ($this->enabled && !$this->readOnly) {
$file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file); $file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file);
$this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG); $this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG);
@ -128,7 +147,7 @@ class Cache
*/ */
public function copyFrom($file, $source) public function copyFrom($file, $source)
{ {
if ($this->enabled) { if ($this->enabled && !$this->readOnly) {
$file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file); $file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file);
$this->filesystem->ensureDirectoryExists(dirname($this->root . $file)); $this->filesystem->ensureDirectoryExists(dirname($this->root . $file));

View File

@ -59,6 +59,7 @@ EOT
continue; continue;
} }
$cache = new Cache($io, $cachePath); $cache = new Cache($io, $cachePath);
$cache->setReadOnly($config->get('cache-read-only'));
if (!$cache->isEnabled()) { if (!$cache->isEnabled()) {
$io->writeError("<info>Cache is not enabled ($key): $cachePath</info>"); $io->writeError("<info>Cache is not enabled ($key): $cachePath</info>");

View File

@ -41,6 +41,7 @@ class Config
'cache-ttl' => 15552000, // 6 months 'cache-ttl' => 15552000, // 6 months
'cache-files-ttl' => null, // fallback to cache-ttl 'cache-files-ttl' => null, // fallback to cache-ttl
'cache-files-maxsize' => '300MiB', 'cache-files-maxsize' => '300MiB',
'cache-read-only' => false,
'bin-compat' => 'auto', 'bin-compat' => 'auto',
'discard-changes' => false, 'discard-changes' => false,
'autoloader-suffix' => null, 'autoloader-suffix' => null,
@ -236,6 +237,7 @@ class Config
return (($flags & self::RELATIVE_PATHS) == self::RELATIVE_PATHS) ? $val : $this->realpath($val); return (($flags & self::RELATIVE_PATHS) == self::RELATIVE_PATHS) ? $val : $this->realpath($val);
// booleans with env var support // booleans with env var support
case 'cache-read-only':
case 'htaccess-protect': case 'htaccess-protect':
// convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
$env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));

View File

@ -187,7 +187,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
$url = reset($urls); $url = reset($urls);
$cacheKey = $url['cacheKey']; $cacheKey = $url['cacheKey'];
if ($cache) { if ($cache && !$cache->isReadOnly()) {
$self->lastCacheWrites[$package->getName()] = $cacheKey; $self->lastCacheWrites[$package->getName()] = $cacheKey;
$cache->copyFrom($cacheKey, $fileName); $cache->copyFrom($cacheKey, $fileName);
} }

View File

@ -477,6 +477,7 @@ class Factory
$cache = null; $cache = null;
if ($config->get('cache-files-ttl') > 0) { if ($config->get('cache-files-ttl') > 0) {
$cache = new Cache($io, $config->get('cache-files-dir'), 'a-z0-9_./'); $cache = new Cache($io, $config->get('cache-files-dir'), 'a-z0-9_./');
$cache->setReadOnly($config->get('cache-read-only'));
} }
$fs = new Filesystem($process); $fs = new Filesystem($process);

View File

@ -130,6 +130,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$this->baseUrl = rtrim(preg_replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/'); $this->baseUrl = rtrim(preg_replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/');
$this->io = $io; $this->io = $io;
$this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$~'); $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$~');
$this->cache->setReadOnly($config->get('cache-read-only'));
$this->versionParser = new VersionParser(); $this->versionParser = new VersionParser();
$this->loader = new ArrayLoader($this->versionParser); $this->loader = new ArrayLoader($this->versionParser);
$this->httpDownloader = $httpDownloader; $this->httpDownloader = $httpDownloader;
@ -1071,7 +1072,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$data = $response->decodeJson(); $data = $response->decodeJson();
HttpDownloader::outputWarnings($this->io, $this->url, $data); HttpDownloader::outputWarnings($this->io, $this->url, $data);
if ($cacheKey) { if ($cacheKey && !$this->cache->isReadOnly()) {
if ($storeLastModifiedTime) { if ($storeLastModifiedTime) {
$lastModifiedDate = $response->getHeader('last-modified'); $lastModifiedDate = $response->getHeader('last-modified');
if ($lastModifiedDate) { if ($lastModifiedDate) {
@ -1155,7 +1156,9 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$data['last-modified'] = $lastModifiedDate; $data['last-modified'] = $lastModifiedDate;
$json = json_encode($data); $json = json_encode($data);
} }
$this->cache->write($cacheKey, $json); if (!$this->cache->isReadOnly()) {
$this->cache->write($cacheKey, $json);
}
return $data; return $data;
} catch (\Exception $e) { } catch (\Exception $e) {
@ -1238,7 +1241,9 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$data['last-modified'] = $lastModifiedDate; $data['last-modified'] = $lastModifiedDate;
$json = JsonFile::encode($data, JsonFile::JSON_UNESCAPED_SLASHES | JsonFile::JSON_UNESCAPED_UNICODE); $json = JsonFile::encode($data, JsonFile::JSON_UNESCAPED_SLASHES | JsonFile::JSON_UNESCAPED_UNICODE);
} }
$cache->write($cacheKey, $json); if (!$cache->isReadOnly()) {
$cache->write($cacheKey, $json);
}
$repo->freshMetadataUrls[$filename] = true; $repo->freshMetadataUrls[$filename] = true;
return $data; return $data;

View File

@ -60,6 +60,7 @@ abstract class BitbucketDriver extends VcsDriver
$this->repository, $this->repository,
)) ))
); );
$this->cache->setReadOnly($this->config->get('cache-read-only'));
} }
/** /**

View File

@ -75,6 +75,7 @@ class GitDriver extends VcsDriver
$this->getBranches(); $this->getBranches();
$this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $cacheUrl)); $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $cacheUrl));
$this->cache->setReadOnly($this->config->get('cache-read-only'));
} }
/** /**

View File

@ -58,6 +58,7 @@ class GitHubDriver extends VcsDriver
$this->originUrl = 'github.com'; $this->originUrl = 'github.com';
} }
$this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository); $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository);
$this->cache->setReadOnly($this->config->get('cache-read-only'));
if ( $this->config->get('use-github-api') === false || (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api'] ) ){ if ( $this->config->get('use-github-api') === false || (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api'] ) ){
$this->setupGitDriver($this->url); $this->setupGitDriver($this->url);

View File

@ -105,6 +105,7 @@ class GitLabDriver extends VcsDriver
$this->repository = preg_replace('#(\.git)$#', '', $match['repo']); $this->repository = preg_replace('#(\.git)$#', '', $match['repo']);
$this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository); $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository);
$this->cache->setReadOnly($this->config->get('cache-read-only'));
$this->fetchProject(); $this->fetchProject();
} }

View File

@ -78,6 +78,7 @@ class SvnDriver extends VcsDriver
} }
$this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->baseUrl)); $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->baseUrl));
$this->cache->setReadOnly($this->config->get('cache-read-only'));
$this->getBranches(); $this->getBranches();
$this->getTags(); $this->getTags();