Skip to content

Commit bf79458

Browse files
committed
feat(lexicon): migrate config key/value
Signed-off-by: Maxence Lange <[email protected]>
1 parent b9480f4 commit bf79458

File tree

17 files changed

+490
-66
lines changed

17 files changed

+490
-66
lines changed

core/Command/Config/App/Base.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
*/
88
namespace OC\Core\Command\Config\App;
99

10+
use OC\Config\ConfigManager;
1011
use OCP\IAppConfig;
1112
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
1213

1314
abstract class Base extends \OC\Core\Command\Base {
1415
public function __construct(
1516
protected IAppConfig $appConfig,
17+
protected readonly ConfigManager $configManager,
1618
) {
1719
parent::__construct();
1820
}

core/Command/Config/App/SetConfig.php

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
namespace OC\Core\Command\Config\App;
1010

1111
use OC\AppConfig;
12-
use OCP\Exceptions\AppConfigIncorrectTypeException;
1312
use OCP\Exceptions\AppConfigUnknownKeyException;
1413
use OCP\IAppConfig;
1514
use Symfony\Component\Console\Helper\QuestionHelper;
@@ -161,7 +160,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
161160
}
162161

163162
$value = (string)$input->getOption('value');
164-
165163
switch ($type) {
166164
case IAppConfig::VALUE_MIXED:
167165
$updated = $this->appConfig->setValueMixed($appName, $configName, $value, $lazy, $sensitive);
@@ -172,34 +170,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int
172170
break;
173171

174172
case IAppConfig::VALUE_INT:
175-
if ($value !== ((string)((int)$value))) {
176-
throw new AppConfigIncorrectTypeException('Value is not an integer');
177-
}
178-
$updated = $this->appConfig->setValueInt($appName, $configName, (int)$value, $lazy, $sensitive);
173+
$updated = $this->appConfig->setValueInt($appName, $configName, $this->configManager->convertToInt($value), $lazy, $sensitive);
179174
break;
180175

181176
case IAppConfig::VALUE_FLOAT:
182-
if ($value !== ((string)((float)$value))) {
183-
throw new AppConfigIncorrectTypeException('Value is not a float');
184-
}
185-
$updated = $this->appConfig->setValueFloat($appName, $configName, (float)$value, $lazy, $sensitive);
177+
$updated = $this->appConfig->setValueFloat($appName, $configName, $this->configManager->convertToFloat($value), $lazy, $sensitive);
186178
break;
187179

188180
case IAppConfig::VALUE_BOOL:
189-
if (in_array(strtolower($value), ['true', '1', 'on', 'yes'])) {
190-
$valueBool = true;
191-
} elseif (in_array(strtolower($value), ['false', '0', 'off', 'no'])) {
192-
$valueBool = false;
193-
} else {
194-
throw new AppConfigIncorrectTypeException('Value is not a boolean, please use \'true\' or \'false\'');
195-
}
196-
$updated = $this->appConfig->setValueBool($appName, $configName, $valueBool, $lazy);
181+
$updated = $this->appConfig->setValueBool($appName, $configName, $this->configManager->convertToBool($value), $lazy);
197182
break;
198183

199184
case IAppConfig::VALUE_ARRAY:
200-
$valueArray = json_decode($value, true, flags: JSON_THROW_ON_ERROR);
201-
$valueArray = (is_array($valueArray)) ? $valueArray : throw new AppConfigIncorrectTypeException('Value is not an array');
202-
$updated = $this->appConfig->setValueArray($appName, $configName, $valueArray, $lazy, $sensitive);
185+
$updated = $this->appConfig->setValueArray($appName, $configName, $this->configManager->convertToArray($value), $lazy, $sensitive);
203186
break;
204187
}
205188
}

core/Command/Config/ListConfigs.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
namespace OC\Core\Command\Config;
99

10+
use OC\Config\ConfigManager;
1011
use OC\Core\Command\Base;
1112
use OC\SystemConfig;
1213
use OCP\IAppConfig;
@@ -22,6 +23,7 @@ class ListConfigs extends Base {
2223
public function __construct(
2324
protected SystemConfig $systemConfig,
2425
protected IAppConfig $appConfig,
26+
protected ConfigManager $configManager,
2527
) {
2628
parent::__construct();
2729
}
@@ -44,13 +46,18 @@ protected function configure() {
4446
InputOption::VALUE_NONE,
4547
'Use this option when you want to include sensitive configs like passwords, salts, ...'
4648
)
49+
->addOption('migrate', null, InputOption::VALUE_NONE, 'Rename config keys of all enabled apps, based on ConfigLexicon')
4750
;
4851
}
4952

5053
protected function execute(InputInterface $input, OutputInterface $output): int {
5154
$app = $input->getArgument('app');
5255
$noSensitiveValues = !$input->getOption('private');
5356

57+
if ($input->getOption('migrate')) {
58+
$this->configManager->migrateConfigLexiconKeys(($app === 'all') ? null : $app);
59+
}
60+
5461
if (!is_string($app)) {
5562
$output->writeln('<error>Invalid app value given</error>');
5663
return 1;

lib/composer/composer/autoload_classmap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,6 +1184,7 @@
11841184
'OC\\Comments\\Manager' => $baseDir . '/lib/private/Comments/Manager.php',
11851185
'OC\\Comments\\ManagerFactory' => $baseDir . '/lib/private/Comments/ManagerFactory.php',
11861186
'OC\\Config' => $baseDir . '/lib/private/Config.php',
1187+
'OC\\Config\\ConfigManager' => $baseDir . '/lib/private/Config/ConfigManager.php',
11871188
'OC\\Config\\Lexicon\\CoreConfigLexicon' => $baseDir . '/lib/private/Config/Lexicon/CoreConfigLexicon.php',
11881189
'OC\\Config\\UserConfig' => $baseDir . '/lib/private/Config/UserConfig.php',
11891190
'OC\\Console\\Application' => $baseDir . '/lib/private/Console/Application.php',
@@ -1889,6 +1890,7 @@
18891890
'OC\\Repair\\ClearGeneratedAvatarCache' => $baseDir . '/lib/private/Repair/ClearGeneratedAvatarCache.php',
18901891
'OC\\Repair\\ClearGeneratedAvatarCacheJob' => $baseDir . '/lib/private/Repair/ClearGeneratedAvatarCacheJob.php',
18911892
'OC\\Repair\\Collation' => $baseDir . '/lib/private/Repair/Collation.php',
1893+
'OC\\Repair\\ConfigKeyMigration' => $baseDir . '/lib/private/Repair/ConfigKeyMigration.php',
18921894
'OC\\Repair\\Events\\RepairAdvanceEvent' => $baseDir . '/lib/private/Repair/Events/RepairAdvanceEvent.php',
18931895
'OC\\Repair\\Events\\RepairErrorEvent' => $baseDir . '/lib/private/Repair/Events/RepairErrorEvent.php',
18941896
'OC\\Repair\\Events\\RepairFinishEvent' => $baseDir . '/lib/private/Repair/Events/RepairFinishEvent.php',

lib/composer/composer/autoload_static.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
12251225
'OC\\Comments\\Manager' => __DIR__ . '/../../..' . '/lib/private/Comments/Manager.php',
12261226
'OC\\Comments\\ManagerFactory' => __DIR__ . '/../../..' . '/lib/private/Comments/ManagerFactory.php',
12271227
'OC\\Config' => __DIR__ . '/../../..' . '/lib/private/Config.php',
1228+
'OC\\Config\\ConfigManager' => __DIR__ . '/../../..' . '/lib/private/Config/ConfigManager.php',
12281229
'OC\\Config\\Lexicon\\CoreConfigLexicon' => __DIR__ . '/../../..' . '/lib/private/Config/Lexicon/CoreConfigLexicon.php',
12291230
'OC\\Config\\UserConfig' => __DIR__ . '/../../..' . '/lib/private/Config/UserConfig.php',
12301231
'OC\\Console\\Application' => __DIR__ . '/../../..' . '/lib/private/Console/Application.php',
@@ -1930,6 +1931,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
19301931
'OC\\Repair\\ClearGeneratedAvatarCache' => __DIR__ . '/../../..' . '/lib/private/Repair/ClearGeneratedAvatarCache.php',
19311932
'OC\\Repair\\ClearGeneratedAvatarCacheJob' => __DIR__ . '/../../..' . '/lib/private/Repair/ClearGeneratedAvatarCacheJob.php',
19321933
'OC\\Repair\\Collation' => __DIR__ . '/../../..' . '/lib/private/Repair/Collation.php',
1934+
'OC\\Repair\\ConfigKeyMigration' => __DIR__ . '/../../..' . '/lib/private/Repair/ConfigKeyMigration.php',
19331935
'OC\\Repair\\Events\\RepairAdvanceEvent' => __DIR__ . '/../../..' . '/lib/private/Repair/Events/RepairAdvanceEvent.php',
19341936
'OC\\Repair\\Events\\RepairErrorEvent' => __DIR__ . '/../../..' . '/lib/private/Repair/Events/RepairErrorEvent.php',
19351937
'OC\\Repair\\Events\\RepairFinishEvent' => __DIR__ . '/../../..' . '/lib/private/Repair/Events/RepairFinishEvent.php',

lib/private/App/AppManager.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
use OC\AppConfig;
1010
use OC\AppFramework\Bootstrap\Coordinator;
11+
use OC\Config\ConfigManager;
1112
use OCP\Activity\IManager as IActivityManager;
1213
use OCP\App\AppPathNotFoundException;
1314
use OCP\App\Events\AppDisableEvent;
@@ -561,6 +562,8 @@ public function enableApp(string $appId, bool $forceEnable = false): void {
561562
ManagerEvent::EVENT_APP_ENABLE, $appId
562563
));
563564
$this->clearAppsCache();
565+
566+
\OCP\Server::get(ConfigManager::class)->migrateConfigLexiconKeys($appId);
564567
}
565568

566569
/**
@@ -615,6 +618,8 @@ public function enableAppForGroups(string $appId, array $groups, bool $forceEnab
615618
ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups
616619
));
617620
$this->clearAppsCache();
621+
622+
\OCP\Server::get(ConfigManager::class)->migrateConfigLexiconKeys($appId);
618623
}
619624

620625
/**

lib/private/AppConfig.php

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@ class AppConfig implements IAppConfig {
5959
private array $valueTypes = []; // type for all config values
6060
private bool $fastLoaded = false;
6161
private bool $lazyLoaded = false;
62-
/** @var array<array-key, array{entries: array<array-key, ConfigLexiconEntry>, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */
62+
/** @var array<array-key, array{entries: array<array-key, ConfigLexiconEntry>, aliases: array<array-key, string>, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */
6363
private array $configLexiconDetails = [];
64+
private bool $ignoreLexiconAliases = false;
6465

6566
/** @var ?array<string, string> */
6667
private ?array $appVersionsCache = null;
@@ -117,6 +118,7 @@ public function getKeys(string $app): array {
117118
public function hasKey(string $app, string $key, ?bool $lazy = false): bool {
118119
$this->assertParams($app, $key);
119120
$this->loadConfig($app, $lazy);
121+
$this->matchAndApplyLexiconDefinition($app, $key);
120122

121123
if ($lazy === null) {
122124
$appCache = $this->getAllValues($app);
@@ -142,6 +144,7 @@ public function hasKey(string $app, string $key, ?bool $lazy = false): bool {
142144
public function isSensitive(string $app, string $key, ?bool $lazy = false): bool {
143145
$this->assertParams($app, $key);
144146
$this->loadConfig(null, $lazy);
147+
$this->matchAndApplyLexiconDefinition($app, $key);
145148

146149
if (!isset($this->valueTypes[$app][$key])) {
147150
throw new AppConfigUnknownKeyException('unknown config key');
@@ -162,6 +165,9 @@ public function isSensitive(string $app, string $key, ?bool $lazy = false): bool
162165
* @since 29.0.0
163166
*/
164167
public function isLazy(string $app, string $key): bool {
168+
$this->assertParams($app, $key);
169+
$this->matchAndApplyLexiconDefinition($app, $key);
170+
165171
// there is a huge probability the non-lazy config are already loaded
166172
if ($this->hasKey($app, $key, false)) {
167173
return false;
@@ -284,7 +290,7 @@ public function getValueMixed(
284290
): string {
285291
try {
286292
$lazy = ($lazy === null) ? $this->isLazy($app, $key) : $lazy;
287-
} catch (AppConfigUnknownKeyException $e) {
293+
} catch (AppConfigUnknownKeyException) {
288294
return $default;
289295
}
290296

@@ -429,6 +435,7 @@ private function getTypedValue(
429435
int $type,
430436
): string {
431437
$this->assertParams($app, $key, valueType: $type);
438+
$origKey = $key;
432439
if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, $default)) {
433440
return $default; // returns default if strictness of lexicon is set to WARNING (block and report)
434441
}
@@ -469,6 +476,15 @@ private function getTypedValue(
469476
$value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
470477
}
471478

479+
// in case the key was modified while running matchAndApplyLexiconDefinition() we are
480+
// interested to check options in case a modification of the value is needed
481+
if ($origKey !== $key) {
482+
$lexiconEntry = $this->getLexiconEntry($app, $key);
483+
if ($type === self::VALUE_BOOL && $lexiconEntry?->hasOption(ConfigLexiconEntry::RENAME_INVERT_BOOLEAN)) {
484+
$value = (in_array(strtolower($value), ['1', 'true', 'yes', 'on'])) ? '0' : '1';
485+
}
486+
}
487+
472488
return $value;
473489
}
474490

@@ -863,7 +879,8 @@ private function setTypedValue(
863879
public function updateType(string $app, string $key, int $type = self::VALUE_MIXED): bool {
864880
$this->assertParams($app, $key);
865881
$this->loadConfigAll();
866-
$lazy = $this->isLazy($app, $key);
882+
$this->matchAndApplyLexiconDefinition($app, $key);
883+
$this->isLazy($app, $key); // confirm key exists
867884

868885
// type can only be one type
869886
if (!in_array($type, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
@@ -905,6 +922,7 @@ public function updateType(string $app, string $key, int $type = self::VALUE_MIX
905922
public function updateSensitive(string $app, string $key, bool $sensitive): bool {
906923
$this->assertParams($app, $key);
907924
$this->loadConfigAll();
925+
$this->matchAndApplyLexiconDefinition($app, $key);
908926

909927
try {
910928
if ($sensitive === $this->isSensitive($app, $key, null)) {
@@ -964,6 +982,7 @@ public function updateSensitive(string $app, string $key, bool $sensitive): bool
964982
public function updateLazy(string $app, string $key, bool $lazy): bool {
965983
$this->assertParams($app, $key);
966984
$this->loadConfigAll();
985+
$this->matchAndApplyLexiconDefinition($app, $key);
967986

968987
try {
969988
if ($lazy === $this->isLazy($app, $key)) {
@@ -999,6 +1018,7 @@ public function updateLazy(string $app, string $key, bool $lazy): bool {
9991018
public function getDetails(string $app, string $key): array {
10001019
$this->assertParams($app, $key);
10011020
$this->loadConfigAll();
1021+
$this->matchAndApplyLexiconDefinition($app, $key);
10021022
$lazy = $this->isLazy($app, $key);
10031023

10041024
if ($lazy) {
@@ -1086,6 +1106,8 @@ public function convertTypeToString(int $type): string {
10861106
*/
10871107
public function deleteKey(string $app, string $key): void {
10881108
$this->assertParams($app, $key);
1109+
$this->matchAndApplyLexiconDefinition($app, $key);
1110+
10891111
$qb = $this->connection->getQueryBuilder();
10901112
$qb->delete('appconfig')
10911113
->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)))
@@ -1293,6 +1315,7 @@ private function setAsLoaded(?bool $lazy): void {
12931315
*/
12941316
public function getValue($app, $key, $default = null) {
12951317
$this->loadConfig($app);
1318+
$this->matchAndApplyLexiconDefinition($app, $key);
12961319

12971320
return $this->fastCache[$app][$key] ?? $default;
12981321
}
@@ -1372,7 +1395,7 @@ private function formatAppValues(string $app, array $values, ?bool $lazy = null)
13721395
foreach ($values as $key => $value) {
13731396
try {
13741397
$type = $this->getValueType($app, $key, $lazy);
1375-
} catch (AppConfigUnknownKeyException $e) {
1398+
} catch (AppConfigUnknownKeyException) {
13761399
continue;
13771400
}
13781401

@@ -1556,17 +1579,18 @@ public function clearCachedConfig(): void {
15561579
}
15571580

15581581
/**
1559-
* match and apply current use of config values with defined lexicon
1582+
* Match and apply current use of config values with defined lexicon.
1583+
* Set $lazy to NULL only if only interested into checking that $key is alias.
15601584
*
15611585
* @throws AppConfigUnknownKeyException
15621586
* @throws AppConfigTypeConflictException
15631587
* @return bool TRUE if everything is fine compared to lexicon or lexicon does not exist
15641588
*/
15651589
private function matchAndApplyLexiconDefinition(
15661590
string $app,
1567-
string $key,
1568-
bool &$lazy,
1569-
int &$type,
1591+
string &$key,
1592+
?bool &$lazy = null,
1593+
int &$type = self::VALUE_MIXED,
15701594
string &$default = '',
15711595
): bool {
15721596
if (in_array($key,
@@ -1578,11 +1602,18 @@ private function matchAndApplyLexiconDefinition(
15781602
return true; // we don't break stuff for this list of config keys.
15791603
}
15801604
$configDetails = $this->getConfigDetailsFromLexicon($app);
1605+
if (array_key_exists($key, $configDetails['aliases']) && !$this->ignoreLexiconAliases) {
1606+
// in case '$rename' is set in ConfigLexiconEntry, we use the new config key
1607+
$key = $configDetails['aliases'][$key];
1608+
}
1609+
15811610
if (!array_key_exists($key, $configDetails['entries'])) {
1582-
return $this->applyLexiconStrictness(
1583-
$configDetails['strictness'],
1584-
'The app config key ' . $app . '/' . $key . ' is not defined in the config lexicon'
1585-
);
1611+
return $this->applyLexiconStrictness($configDetails['strictness'], 'The app config key ' . $app . '/' . $key . ' is not defined in the config lexicon');
1612+
}
1613+
1614+
// if lazy is NULL, we ignore all check on the type/lazyness/default from Lexicon
1615+
if ($lazy === null) {
1616+
return true;
15861617
}
15871618

15881619
/** @var ConfigLexiconEntry $configValue */
@@ -1644,27 +1675,45 @@ private function applyLexiconStrictness(
16441675
* extract details from registered $appId's config lexicon
16451676
*
16461677
* @param string $appId
1678+
* @internal
16471679
*
1648-
* @return array{entries: array<array-key, ConfigLexiconEntry>, strictness: ConfigLexiconStrictness}
1680+
* @return array{entries: array<array-key, ConfigLexiconEntry>, aliases: array<array-key, string>, strictness: ConfigLexiconStrictness}
16491681
*/
1650-
private function getConfigDetailsFromLexicon(string $appId): array {
1682+
public function getConfigDetailsFromLexicon(string $appId): array {
16511683
if (!array_key_exists($appId, $this->configLexiconDetails)) {
1652-
$entries = [];
1684+
$entries = $aliases = [];
16531685
$bootstrapCoordinator = \OCP\Server::get(Coordinator::class);
16541686
$configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId);
16551687
foreach ($configLexicon?->getAppConfigs() ?? [] as $configEntry) {
16561688
$entries[$configEntry->getKey()] = $configEntry;
1689+
if ($configEntry->getRename() !== null) {
1690+
$aliases[$configEntry->getRename()] = $configEntry->getKey();
1691+
}
16571692
}
16581693

16591694
$this->configLexiconDetails[$appId] = [
16601695
'entries' => $entries,
1696+
'aliases' => $aliases,
16611697
'strictness' => $configLexicon?->getStrictness() ?? ConfigLexiconStrictness::IGNORE
16621698
];
16631699
}
16641700

16651701
return $this->configLexiconDetails[$appId];
16661702
}
16671703

1704+
private function getLexiconEntry(string $appId, string $key): ?ConfigLexiconEntry {
1705+
return $this->getConfigDetailsFromLexicon($appId)['entries'][$key] ?? null;
1706+
}
1707+
1708+
/**
1709+
* if set to TRUE, ignore aliases defined in Config Lexicon during the use of the methods of this class
1710+
*
1711+
* @internal
1712+
*/
1713+
public function ignoreLexiconAliases(bool $ignore): void {
1714+
$this->ignoreLexiconAliases = $ignore;
1715+
}
1716+
16681717
/**
16691718
* Returns the installed versions of all apps
16701719
*

0 commit comments

Comments
 (0)