Skip to content

Commit

Permalink
Add support for global config
Browse files Browse the repository at this point in the history
  • Loading branch information
pnkov committed Aug 21, 2024
1 parent 01dddda commit 3370c90
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 67 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,21 @@ The new version continues to support parsing the unique repository configuration
The `username` and `password` can be specified in the `auth.json` file on a per-user basis with the [authentication mechanism provided by Composer](https://getcomposer.org/doc/articles/http-basic-authentication.md).
### Global configuration
It's also possible to add some configuration inside global `composer.json` located at composer home (`composer config -g home`).
Following precedence order will be used for each key:
- command-line parameter
- local `composer.json`
- global `composer.json`
- default
Array values will not be merged.
The command-line parameter -- repository is required if local configuration is multi repository. Global unique repository configuration will be ignored in that case.
Multi repository configuration will be merged by the `name` key.
## Providers
Specificity for some of the providers.
Expand Down
88 changes: 52 additions & 36 deletions src/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -279,58 +279,74 @@ private function getComposerJsonArchiveExcludeIgnores(InputInterface $input)

/**
* @param InputInterface $input
* @param Composer $composer
*
* @return array
* @throws \InvalidArgumentException|InvalidConfigException
*/
private function parseNexusExtra(InputInterface $input, Composer $composer)
{
$this->checkNexusPushValid($input, $composer);

$repository = $input->getOption(PushCommand::REPOSITORY);
$extras = $composer->getPackage()->getExtra();

$extrasConfigurationKey = 'push';

if (empty($extras['push'])) {
if (!empty($extras['nexus-push'])) {
$extrasConfigurationKey = 'nexus-push';
$globalComposer = $composer->getPluginManager()->getGlobalComposer();
$globalExtras = !empty($globalComposer) ? $globalComposer->getPackage()->getExtra() : null;
$localExtras = $composer->getPackage()->getExtra();

$localExtrasConfigurationKey = 'push';
if (empty($localExtras['push'])) {
if (!empty($localExtras['nexus-push'])) {
$localExtrasConfigurationKey = 'nexus-push';
$this->io->warning('Configuration under extra - nexus-push in composer.json is deprecated, please replace it by extra - push');
}
}

if (empty($repository)) {
// configurations in composer.json support Only upload to unique repository
if (!empty($extras[$extrasConfigurationKey])) {
return $extras[$extrasConfigurationKey];
}
} else {
// configurations in composer.json support upload to multi repository
foreach ($extras[$extrasConfigurationKey] as $key => $nexusPushConfigItem) {
if (empty($nexusPushConfigItem[self::PUSH_CFG_NAME])) {
$fmt = 'The push configuration array in composer.json with index {%s} need provide value for key "%s"';
$exceptionMsg = sprintf($fmt, $key, self::PUSH_CFG_NAME);
throw new InvalidConfigException($exceptionMsg);
}
if ($nexusPushConfigItem[self::PUSH_CFG_NAME] == $repository) {
return $nexusPushConfigItem;
}
}
$globalConfig = !empty($globalExtras['push']) ? $globalExtras['push'] : null;
$localConfig = !empty($localExtras[$localExtrasConfigurationKey]) ? $localExtras[$localExtrasConfigurationKey] : null;

$repository = $input->getOption(PushCommand::REPOSITORY);
if (empty($repository) && !empty($localConfig[0])) {
throw new \InvalidArgumentException('As configurations in composer.json support upload to multi repository, the option --repository is required');
}
if (!empty($repository) && empty($globalConfig[0]) && empty($localConfig[0])) {
throw new InvalidConfigException('the option --repository is offered, but configurations in composer.json doesn\'t support upload to multi repository, please check');
}

if (empty($this->nexusPushConfig)) {
if (!empty($repository)) {
$globalRepository = $this->getRepositoryConfig($globalConfig, $repository);
$localRepository = $this->getRepositoryConfig($localConfig, $repository);

if (empty($globalRepository) && empty($localRepository)) {
throw new \InvalidArgumentException('The value of option --repository match no push configuration, please check');
}

return array_replace($globalRepository ?? [], $localRepository ?? []);
}

return [];
return array_replace($globalConfig ?? [], $localConfig ?? []);
}

private function checkNexusPushValid(InputInterface $input, Composer $composer)
/**
* @param mixed $extras
* @param string $name
*
* @return mixed|null
* @throws InvalidConfigException
*/
private function getRepositoryConfig($extras, $name)
{
$repository = $input->getOption(PushCommand::REPOSITORY);
$extras = $composer->getPackage()->getExtra();
if (empty($repository) && (!empty($extras['push'][0]) || !empty($extras['nexus-push'][0]))) {
throw new \InvalidArgumentException('As configurations in composer.json support upload to multi repository, the option --repository is required');
if (empty($extras[0])) {
return null;
}
if (!empty($repository) && empty($extras['push'][0]) && empty($extras['nexus-push'][0])) {
throw new InvalidConfigException('the option --repository is offered, but configurations in composer.json doesn\'t support upload to multi repository, please check');

foreach ($extras as $key => $repository) {
if (empty($repository[self::PUSH_CFG_NAME])) {
$fmt = 'The push configuration array in composer.json with index {%s} need provide value for key "%s"';
$exceptionMsg = sprintf($fmt, $key, self::PUSH_CFG_NAME);
throw new InvalidConfigException($exceptionMsg);
}
if ($repository[self::PUSH_CFG_NAME] === $name) {
return $repository;
}
}

return null;
}
}
207 changes: 176 additions & 31 deletions tests/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
use Composer\Composer;
use Composer\IO\NullIO;
use Composer\Package\RootPackageInterface;
use Composer\PartialComposer;
use Composer\Plugin\PluginManager;
use PHPUnit\Framework\ExpectationFailedException;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Input\InputInterface;
Expand Down Expand Up @@ -36,7 +38,9 @@ class ConfigurationTest extends TestCase
private $configIgnoreByComposer;
private $configOptionUrl;

private $singleConfig;
private $localConfig;
private $globalConfig;
private $splitConfig;
private $repository;

private $configType;
Expand All @@ -45,6 +49,10 @@ class ConfigurationTest extends TestCase
private $configVerifySsl;
private $extraVerifySsl;

private const ComposerConfigEmpty = 0;
private const ComposerConfigSingle = 1;
private const ComposerConfigMulti = 2;

public function setUp(): void
{
$this->keepVendor = null;
Expand All @@ -53,7 +61,8 @@ public function setUp(): void
$this->configIgnoreByComposer = null;
$this->configOptionUrl = "https://option-url.com";

$this->singleConfig = true;
$this->localConfig = self::ComposerConfigSingle;
$this->globalConfig = self::ComposerConfigEmpty;
$this->configName = null;

$this->configType = null;
Expand Down Expand Up @@ -145,7 +154,7 @@ public function testGet()
$this->assertEquals('push-username', $this->configuration->get('username'));
$this->assertEquals('push-password', $this->configuration->get('password'));

$this->singleConfig = false;
$this->localConfig = self::ComposerConfigMulti;
$this->repository = 'A';

$this->initGlobalConfiguration();
Expand Down Expand Up @@ -256,6 +265,85 @@ public function testGetOptionUsername()
$this->assertEquals("my-username", $this->configuration->getOptionUsername());
}

public function testGetGlobalConfig()
{
$this->configIgnore = ['dir1', 'dir2'];

$this->splitConfig = true;
$this->localConfig = self::ComposerConfigSingle;
$this->globalConfig = self::ComposerConfigSingle;
$this->repository = null;

$this->initGlobalConfiguration();
$this->assertEquals('https://global.example.com', $this->configuration->get('url'));
$this->assertArrayEquals($this->configIgnore, $this->configuration->get('ignore'));

$this->splitConfig = false;
$this->localConfig = self::ComposerConfigSingle;
$this->globalConfig = self::ComposerConfigMulti;
$this->repository = null;

$this->initGlobalConfiguration();
$this->assertEquals('https://example.com', $this->configuration->get('url'));

$this->repository = 'A';

$this->initGlobalConfiguration();
$this->assertEquals('https://global.a.com', $this->configuration->get('url'));

$this->repository = 'B';

$this->initGlobalConfiguration();
$this->assertEquals('https://global.b.com', $this->configuration->get('url'));

$this->localConfig = self::ComposerConfigMulti;
$this->globalConfig = self::ComposerConfigSingle;
$this->repository = null;

$this->initGlobalConfiguration();
$this->expectException(\InvalidArgumentException::class);
$this->configuration->get('url');

$this->localConfig = self::ComposerConfigMulti;
$this->globalConfig = self::ComposerConfigMulti;
$this->repository = 'A';

$this->initGlobalConfiguration();
$this->assertEquals('https://a.com', $this->configuration->get('url'));
$this->assertEquals('global-push-username-a', $this->configuration->get('username'));

$this->repository = 'B';

$this->initGlobalConfiguration();
$this->assertEquals('https://b.com', $this->configuration->get('url'));
$this->assertEquals('global-push-username-b', $this->configuration->get('username'));


$this->splitConfig = false;

$this->localConfig = self::ComposerConfigEmpty;
$this->globalConfig = self::ComposerConfigSingle;
$this->repository = null;

$this->initGlobalConfiguration();
$this->assertEquals('https://global.example.com', $this->configuration->get('url'));
$this->assertEquals(null, $this->configuration->get('ignore'));

$this->localConfig = self::ComposerConfigEmpty;
$this->globalConfig = self::ComposerConfigMulti;
$this->repository = 'A';

$this->initGlobalConfiguration();
$this->assertEquals('https://global.a.com', $this->configuration->get('url'));
$this->assertEquals('global-push-username-a', $this->configuration->get('username'));

$this->repository = 'B';

$this->initGlobalConfiguration();
$this->assertEquals('https://global.b.com', $this->configuration->get('url'));
$this->assertEquals('global-push-username-b', $this->configuration->get('username'));
}

private function createInputMock()
{
$input = $this->createMock(InputInterface::class);
Expand Down Expand Up @@ -311,36 +399,93 @@ private function createComposerMock()

$packageInterface->method('getVersion')->willReturn('1.2.3');
$packageInterface->method('getExtra')->willReturnCallback(function() {
if ($this->singleConfig) {
return [
'push' => [
'url' => 'https://example.com',
"username" => "push-username",
"password" => "push-password",
"ignore" => $this->configIgnore,
"type" => $this->extraConfigType,
"ssl-verify" => $this->extraVerifySsl,
]
];
} else {
return [
'push' => [
[
'name' => 'A',
'url' => 'https://a.com',
"username" => "push-username-a",
"password" => "push-password-a",
],
[
'name' => 'B',
'url' => 'https://b.com',
"username" => "push-username-b",
"password" => "push-password-b",
]
]
];
switch ($this->localConfig) {
case self::ComposerConfigSingle:
return [
'push' => array_replace([
"ignore" => $this->configIgnore,
], (!$this->splitConfig) ? [
'url' => 'https://example.com',
"username" => "push-username",
"password" => "push-password",
"type" => $this->extraConfigType,
"ssl-verify" => $this->extraVerifySsl,
] : [])
];
case self::ComposerConfigMulti:
return [
'push' => array_replace_recursive([
[
'name' => 'A',
'url' => 'https://a.com',
],
[
'name' => 'B',
'url' => 'https://b.com',
]
], (!$this->splitConfig) ? [
[
"username" => "push-username-a",
"password" => "push-password-a",
],
[
"username" => "push-username-b",
"password" => "push-password-b",
]
] : [])
];
default:
return [];
}
});

$pluginManager = $this->createMock(PluginManager::class);
$globalComposer = $this->createMock(PartialComposer::class);
$globalPackageInterface = $this->createMock(RootPackageInterface::class);

$composer->method('getPluginManager')->willReturn($pluginManager);
$pluginManager->method('getGlobalComposer')->willReturn($globalComposer);
$globalComposer->method('getPackage')->willReturn($globalPackageInterface);

$globalPackageInterface->method('getExtra')->willReturnCallback(function () {
switch ($this->globalConfig) {
case self::ComposerConfigSingle:
return [
'push' => array_replace([
'url' => 'https://global.example.com',
"username" => "global-push-username",
"password" => "global-push-password",
"type" => $this->extraConfigType,
"ssl-verify" => $this->extraVerifySsl,
], (!$this->splitConfig) ? [
"ignore" => $this->configIgnore,
] : [])
];
case self::ComposerConfigMulti:
return [
'push' => array_replace_recursive([
[
'name' => 'B',
"username" => "global-push-username-b",
"password" => "global-push-password-b",
],
[
'name' => 'A',
"username" => "global-push-username-a",
"password" => "global-push-password-a",
]
], (!$this->splitConfig) ? [
[
'url' => 'https://global.b.com',
],
[
'url' => 'https://global.a.com',
]
] : [])
];
default:
return [];
}
});

$packageInterface->method('getArchiveExcludes')->willReturnCallback(function() {
Expand Down

0 comments on commit 3370c90

Please sign in to comment.