Skip to content

Commit

Permalink
fix: Don't rely on inaccessible packages when fetching update info.
Browse files Browse the repository at this point in the history
Composer wants to use information about all dependencies when reporting the most up-to-date version that meets the project's stability/version constraints and doesn't conflict with any other dependencies.
If a dependency is inaccessible (e.g. private repositories or IP-restricted hosting) composer will fail to declare a version candidate, which results in missing information even for some accessible packages.

By ommitting inaccessible repositories, we do at least get _some_ version information, even if there is a change of compatability issues.
  • Loading branch information
GuySartorelli committed Dec 13, 2021
1 parent ba8bae9 commit 23d7e5f
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 8 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ will need to have the necessary permissions to access the private VCS repositori
update information about necessary updates to the module.

If the process looking for available updates fails (for example, due to an authentication failure against a private
repository) the process will fail gracefully and allow the rest of the report generation to continue.
repository) the process will fail gracefully and allow the rest of the report generation to continue. However, this
can result in incomplete information being fetched about non-private repositories due to the way composer checks for
conflicts between packages. For this reason, if you cannot supply authentication details for private repositories,
you should mark those repositories as inaccessible as per the documentation in the [SilverStripe Maintenance module](https://github.com/bringyourownideas/silverstripe-maintenance#private-repositories).

Users on the [Common Web Platform](https://cwp.govt.nz) will currently not be able to retrieve information about
updates to private repositories.
Expand Down
74 changes: 74 additions & 0 deletions src/DriverReflection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

namespace BringYourOwnIdeas\UpdateChecker;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Repository\VcsRepository;
use ReflectionObject;
use RuntimeException;

class DriverReflection
{
public static function getDriverWithoutException(VcsRepository $repo, IOInterface $io, Config $config)
{
$reflectedRepo = new ReflectionObject($repo);
$drivers = static::getRepoField($repo, $reflectedRepo, 'drivers');

if (isset($drivers[static::getRepoField($repo, $reflectedRepo, 'type')])) {
$class = $drivers[static::getRepoField($repo, $reflectedRepo, 'type')];
$driver = new $class($repo->getRepoConfig(), $io, $config);
try {
$driver->initialize();
} catch (RuntimeException $e) {
// no-op - this is probably caused due to insufficient permissions when trying to create /var/www/.ssh
// but since we're just getting the driver as a shortcut to getting the repository name, we can ignore this for now.
}
return $driver;
}

foreach ($drivers as $driver) {
if ($driver::supports($io, $config, static::getRepoField($repo, $reflectedRepo, 'url'))) {
$driver = new $driver($repo->getRepoConfig(), $io, $config);
try {
$driver->initialize();
} catch (RuntimeException $e) {
// no-op - this is probably caused due to insufficient permissions when trying to create /var/www/.ssh
// but since we're just getting the driver as a shortcut to getting the repository name, we can ignore this for now.
}
return $driver;
}
}

foreach ($drivers as $driver) {
if ($driver::supports($io, $config, static::getRepoField($repo, $reflectedRepo, 'url'), true)) {
$driver = new $driver($repo->getRepoConfig(), $io, $config);
try {
$driver->initialize();
} catch (RuntimeException $e) {
// no-op - this is probably caused due to insufficient permissions when trying to create /var/www/.ssh
// but since we're just getting the driver as a shortcut to getting the repository name, we can ignore this for now.
}
return $driver;
}
}
}

public static function getSshUrl($driver)
{
$reflectedDriver = new ReflectionObject($driver);
if ($reflectedDriver->hasMethod('generateSshUrl')) {
$reflectedMethod = $reflectedDriver->getMethod('generateSshUrl');
$reflectedMethod->setAccessible(true);
return $reflectedMethod->invoke($driver);
}
return null;
}

protected static function getRepoField(VcsRepository $repo, ReflectionObject $reflectedRepo, string $field)
{
$reflectedUrl = $reflectedRepo->getProperty($field);
$reflectedUrl->setAccessible(true);
return $reflectedUrl->getValue($repo);
}
}
73 changes: 66 additions & 7 deletions src/Extensions/ComposerLoaderExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace BringYourOwnIdeas\UpdateChecker\Extensions;

use BringYourOwnIdeas\Maintenance\Tasks\UpdatePackageInfoTask;
use BringYourOwnIdeas\UpdateChecker\DriverReflection;
use Composer\Composer;
use Composer\Factory;
use Composer\IO\NullIO;
Expand All @@ -10,6 +12,9 @@
use Composer\Repository\BaseRepository;
use Composer\Repository\CompositeRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RepositoryManager;
use Composer\Repository\Vcs\VcsDriverInterface;
use Composer\Repository\VcsRepository;
use SilverStripe\Core\Environment;
use SilverStripe\Core\Extension;

Expand Down Expand Up @@ -120,17 +125,71 @@ public function onAfterBuild()
}

$originalDir = getcwd();

if ($originalDir !== BASE_PATH) {
chdir(BASE_PATH);
chdir(BASE_PATH);
/** @var Composer $composer */
$composer = Factory::create($io = new NullIO());

// Don't include inaccessible repositories.
$inaccessiblePackages = (array)UpdatePackageInfoTask::config()->get('inaccessible_packages');
$inaccessibleHosts = (array)UpdatePackageInfoTask::config()->get('inaccessible_repository_hosts');
if (!empty($inaccessiblePackages) || !empty($inaccessibleHosts)) {
$oldManager = $composer->getRepositoryManager();
$manager = new RepositoryManager(
$io,
$composer->getConfig(),
$composer->getEventDispatcher(),
Factory::createRemoteFilesystem($io, $composer->getConfig())
);
$manager->setLocalRepository($oldManager->getLocalRepository());
foreach ($oldManager->getRepositories() as $repo) {
if ($repo instanceof VcsRepository) {
/** @var VcsDriverInterface $driver */
$driver = DriverReflection::getDriverWithoutException($repo, $io, $composer->getConfig());
$sshUrl = DriverReflection::getSshUrl($driver);
$sourceURL = $driver->getUrl();
$package = $this->findPackageByUrl($sourceURL);
if (!$package && $sshUrl) {
$package = $this->findPackageByUrl($sshUrl);
}
// Don't add the repository if we can confirm it's inaccessible.
// Otherwise the UpdateChecker will attempt to fetch packages using the VcsDriver.
if (
($package && in_array($package->name, $inaccessiblePackages))
|| in_array(parse_url($sourceURL, PHP_URL_HOST), $inaccessibleHosts)
|| ($sshUrl && in_array(preg_replace('/^([^@]+@)?([^:]+):.*/', '$2', $sshUrl), $inaccessibleHosts))
) {
continue;
}
}
$manager->addRepository($repo);
}
$composer->setRepositoryManager($manager);
}

/** @var Composer $composer */
$composer = Factory::create(new NullIO());
$this->setComposer($composer);
chdir($originalDir);
}

if ($originalDir !== BASE_PATH) {
chdir($originalDir);
public function findPackageByUrl(string $url, bool $includeDev = true)
{
$lock = $this->owner->getLock();
foreach ($lock->packages as $package) {
if (isset($package->source->url) && $package->source->url === $url) {
return $package;
}
if (isset($package->dist->url) && $package->dist->url === $url) {
return $package;
}
}
if ($includeDev) {
foreach ($lock->{'packages-dev'} as $package) {
if (isset($package->source->url) && $package->source->url === $url) {
return $package;
}
if (isset($package->dist->url) && $package->dist->url === $url) {
return $package;
}
}
}
}
}

0 comments on commit 23d7e5f

Please sign in to comment.