diff --git a/README.md b/README.md index 7104e29..b447338 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/DriverReflection.php b/src/DriverReflection.php new file mode 100644 index 0000000..dc2e8c1 --- /dev/null +++ b/src/DriverReflection.php @@ -0,0 +1,74 @@ +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); + } +} diff --git a/src/Extensions/ComposerLoaderExtension.php b/src/Extensions/ComposerLoaderExtension.php index 7ce9614..e32b0fa 100644 --- a/src/Extensions/ComposerLoaderExtension.php +++ b/src/Extensions/ComposerLoaderExtension.php @@ -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; @@ -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; @@ -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; + } + } } } }