Skip to content

Commit 23d7e5f

Browse files
committed
fix: Don't rely on inaccessible packages when fetching update info.
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.
1 parent ba8bae9 commit 23d7e5f

File tree

3 files changed

+144
-8
lines changed

3 files changed

+144
-8
lines changed

README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ will need to have the necessary permissions to access the private VCS repositori
4848
update information about necessary updates to the module.
4949

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

5356
Users on the [Common Web Platform](https://cwp.govt.nz) will currently not be able to retrieve information about
5457
updates to private repositories.

src/DriverReflection.php

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
namespace BringYourOwnIdeas\UpdateChecker;
4+
5+
use Composer\Config;
6+
use Composer\IO\IOInterface;
7+
use Composer\Repository\VcsRepository;
8+
use ReflectionObject;
9+
use RuntimeException;
10+
11+
class DriverReflection
12+
{
13+
public static function getDriverWithoutException(VcsRepository $repo, IOInterface $io, Config $config)
14+
{
15+
$reflectedRepo = new ReflectionObject($repo);
16+
$drivers = static::getRepoField($repo, $reflectedRepo, 'drivers');
17+
18+
if (isset($drivers[static::getRepoField($repo, $reflectedRepo, 'type')])) {
19+
$class = $drivers[static::getRepoField($repo, $reflectedRepo, 'type')];
20+
$driver = new $class($repo->getRepoConfig(), $io, $config);
21+
try {
22+
$driver->initialize();
23+
} catch (RuntimeException $e) {
24+
// no-op - this is probably caused due to insufficient permissions when trying to create /var/www/.ssh
25+
// but since we're just getting the driver as a shortcut to getting the repository name, we can ignore this for now.
26+
}
27+
return $driver;
28+
}
29+
30+
foreach ($drivers as $driver) {
31+
if ($driver::supports($io, $config, static::getRepoField($repo, $reflectedRepo, 'url'))) {
32+
$driver = new $driver($repo->getRepoConfig(), $io, $config);
33+
try {
34+
$driver->initialize();
35+
} catch (RuntimeException $e) {
36+
// no-op - this is probably caused due to insufficient permissions when trying to create /var/www/.ssh
37+
// but since we're just getting the driver as a shortcut to getting the repository name, we can ignore this for now.
38+
}
39+
return $driver;
40+
}
41+
}
42+
43+
foreach ($drivers as $driver) {
44+
if ($driver::supports($io, $config, static::getRepoField($repo, $reflectedRepo, 'url'), true)) {
45+
$driver = new $driver($repo->getRepoConfig(), $io, $config);
46+
try {
47+
$driver->initialize();
48+
} catch (RuntimeException $e) {
49+
// no-op - this is probably caused due to insufficient permissions when trying to create /var/www/.ssh
50+
// but since we're just getting the driver as a shortcut to getting the repository name, we can ignore this for now.
51+
}
52+
return $driver;
53+
}
54+
}
55+
}
56+
57+
public static function getSshUrl($driver)
58+
{
59+
$reflectedDriver = new ReflectionObject($driver);
60+
if ($reflectedDriver->hasMethod('generateSshUrl')) {
61+
$reflectedMethod = $reflectedDriver->getMethod('generateSshUrl');
62+
$reflectedMethod->setAccessible(true);
63+
return $reflectedMethod->invoke($driver);
64+
}
65+
return null;
66+
}
67+
68+
protected static function getRepoField(VcsRepository $repo, ReflectionObject $reflectedRepo, string $field)
69+
{
70+
$reflectedUrl = $reflectedRepo->getProperty($field);
71+
$reflectedUrl->setAccessible(true);
72+
return $reflectedUrl->getValue($repo);
73+
}
74+
}

src/Extensions/ComposerLoaderExtension.php

+66-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace BringYourOwnIdeas\UpdateChecker\Extensions;
44

5+
use BringYourOwnIdeas\Maintenance\Tasks\UpdatePackageInfoTask;
6+
use BringYourOwnIdeas\UpdateChecker\DriverReflection;
57
use Composer\Composer;
68
use Composer\Factory;
79
use Composer\IO\NullIO;
@@ -10,6 +12,9 @@
1012
use Composer\Repository\BaseRepository;
1113
use Composer\Repository\CompositeRepository;
1214
use Composer\Repository\RepositoryInterface;
15+
use Composer\Repository\RepositoryManager;
16+
use Composer\Repository\Vcs\VcsDriverInterface;
17+
use Composer\Repository\VcsRepository;
1318
use SilverStripe\Core\Environment;
1419
use SilverStripe\Core\Extension;
1520

@@ -120,17 +125,71 @@ public function onAfterBuild()
120125
}
121126

122127
$originalDir = getcwd();
123-
124-
if ($originalDir !== BASE_PATH) {
125-
chdir(BASE_PATH);
128+
chdir(BASE_PATH);
129+
/** @var Composer $composer */
130+
$composer = Factory::create($io = new NullIO());
131+
132+
// Don't include inaccessible repositories.
133+
$inaccessiblePackages = (array)UpdatePackageInfoTask::config()->get('inaccessible_packages');
134+
$inaccessibleHosts = (array)UpdatePackageInfoTask::config()->get('inaccessible_repository_hosts');
135+
if (!empty($inaccessiblePackages) || !empty($inaccessibleHosts)) {
136+
$oldManager = $composer->getRepositoryManager();
137+
$manager = new RepositoryManager(
138+
$io,
139+
$composer->getConfig(),
140+
$composer->getEventDispatcher(),
141+
Factory::createRemoteFilesystem($io, $composer->getConfig())
142+
);
143+
$manager->setLocalRepository($oldManager->getLocalRepository());
144+
foreach ($oldManager->getRepositories() as $repo) {
145+
if ($repo instanceof VcsRepository) {
146+
/** @var VcsDriverInterface $driver */
147+
$driver = DriverReflection::getDriverWithoutException($repo, $io, $composer->getConfig());
148+
$sshUrl = DriverReflection::getSshUrl($driver);
149+
$sourceURL = $driver->getUrl();
150+
$package = $this->findPackageByUrl($sourceURL);
151+
if (!$package && $sshUrl) {
152+
$package = $this->findPackageByUrl($sshUrl);
153+
}
154+
// Don't add the repository if we can confirm it's inaccessible.
155+
// Otherwise the UpdateChecker will attempt to fetch packages using the VcsDriver.
156+
if (
157+
($package && in_array($package->name, $inaccessiblePackages))
158+
|| in_array(parse_url($sourceURL, PHP_URL_HOST), $inaccessibleHosts)
159+
|| ($sshUrl && in_array(preg_replace('/^([^@]+@)?([^:]+):.*/', '$2', $sshUrl), $inaccessibleHosts))
160+
) {
161+
continue;
162+
}
163+
}
164+
$manager->addRepository($repo);
165+
}
166+
$composer->setRepositoryManager($manager);
126167
}
127168

128-
/** @var Composer $composer */
129-
$composer = Factory::create(new NullIO());
130169
$this->setComposer($composer);
170+
chdir($originalDir);
171+
}
131172

132-
if ($originalDir !== BASE_PATH) {
133-
chdir($originalDir);
173+
public function findPackageByUrl(string $url, bool $includeDev = true)
174+
{
175+
$lock = $this->owner->getLock();
176+
foreach ($lock->packages as $package) {
177+
if (isset($package->source->url) && $package->source->url === $url) {
178+
return $package;
179+
}
180+
if (isset($package->dist->url) && $package->dist->url === $url) {
181+
return $package;
182+
}
183+
}
184+
if ($includeDev) {
185+
foreach ($lock->{'packages-dev'} as $package) {
186+
if (isset($package->source->url) && $package->source->url === $url) {
187+
return $package;
188+
}
189+
if (isset($package->dist->url) && $package->dist->url === $url) {
190+
return $package;
191+
}
192+
}
134193
}
135194
}
136195
}

0 commit comments

Comments
 (0)