Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: allow object store configuration aliases for easier migrations
Signed-off-by: Robin Appelman <robin@icewind.nl>
  • Loading branch information
icewind1991 committed Nov 20, 2025
commit f21dcac9ef09105a03fa62af63f075d94d99b80a
1 change: 1 addition & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -1645,6 +1645,7 @@
'OC\\Files\\ObjectStore\\Azure' => $baseDir . '/lib/private/Files/ObjectStore/Azure.php',
'OC\\Files\\ObjectStore\\HomeObjectStoreStorage' => $baseDir . '/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php',
'OC\\Files\\ObjectStore\\IObjectStoreMetaData' => $baseDir . '/lib/private/Files/ObjectStore/IObjectStoreMetaData.php',
'OC\\Files\\ObjectStore\\InvalidObjectStoreConfigurationException' => $baseDir . '/lib/private/Files/ObjectStore/InvalidObjectStoreConfigurationException.php',
'OC\\Files\\ObjectStore\\Mapper' => $baseDir . '/lib/private/Files/ObjectStore/Mapper.php',
'OC\\Files\\ObjectStore\\ObjectStoreScanner' => $baseDir . '/lib/private/Files/ObjectStore/ObjectStoreScanner.php',
'OC\\Files\\ObjectStore\\ObjectStoreStorage' => $baseDir . '/lib/private/Files/ObjectStore/ObjectStoreStorage.php',
Expand Down
1 change: 1 addition & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -1694,6 +1694,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Files\\ObjectStore\\Azure' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/Azure.php',
'OC\\Files\\ObjectStore\\HomeObjectStoreStorage' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php',
'OC\\Files\\ObjectStore\\IObjectStoreMetaData' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/IObjectStoreMetaData.php',
'OC\\Files\\ObjectStore\\InvalidObjectStoreConfigurationException' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/InvalidObjectStoreConfigurationException.php',
'OC\\Files\\ObjectStore\\Mapper' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/Mapper.php',
'OC\\Files\\ObjectStore\\ObjectStoreScanner' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/ObjectStoreScanner.php',
'OC\\Files\\ObjectStore\\ObjectStoreStorage' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/ObjectStoreStorage.php',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OC\Files\ObjectStore;

class InvalidObjectStoreConfigurationException extends \Exception {

}
100 changes: 77 additions & 23 deletions lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,11 @@ public function buildObjectStore(array $config): IObjectStore {
* @return ?ObjectStoreConfig
*/
public function getObjectStoreConfigForRoot(): ?array {
$configs = $this->getObjectStoreConfig();
if (!$configs) {
if (!$this->hasObjectStore()) {
return null;
}

$config = $configs['root'] ?? $configs['default'];
$config = $this->getObjectStoreConfiguration('root');

if ($config['arguments']['multibucket']) {
if (!isset($config['arguments']['bucket'])) {
Expand All @@ -56,17 +55,12 @@ public function getObjectStoreConfigForRoot(): ?array {
* @return ?ObjectStoreConfig
*/
public function getObjectStoreConfigForUser(IUser $user): ?array {
$configs = $this->getObjectStoreConfig();
if (!$configs) {
if (!$this->hasObjectStore()) {
return null;
}

$store = $this->getObjectStoreForUser($user);

if (!isset($configs[$store])) {
throw new \Exception("Object store configuration for '{$store}' not found");
}
$config = $configs[$store];
$config = $this->getObjectStoreConfiguration($store);

if ($config['arguments']['multibucket']) {
$config['arguments']['bucket'] = $this->getBucketForUser($user, $config);
Expand All @@ -75,55 +69,106 @@ public function getObjectStoreConfigForUser(IUser $user): ?array {
}

/**
* @return ?array<string, ObjectStoreConfig>
* @param string $name
* @return ObjectStoreConfig
*/
public function getObjectStoreConfiguration(string $name): array {
$configs = $this->getObjectStoreConfigs();
$name = $this->resolveAlias($name);
if (!isset($configs[$name])) {
throw new \Exception("Object store configuration for '$name' not found");
}
if (is_string($configs[$name])) {
throw new \Exception("Object store configuration for '{$configs[$name]}' not found");
}
return $configs[$name];
}

public function resolveAlias(string $name): string {
$configs = $this->getObjectStoreConfigs();

while (isset($configs[$name]) && is_string($configs[$name])) {
$name = $configs[$name];
}
return $name;
}

public function hasObjectStore(): bool {
$objectStore = $this->config->getSystemValue('objectstore', null);
$objectStoreMultiBucket = $this->config->getSystemValue('objectstore_multibucket', null);
return $objectStore || $objectStoreMultiBucket;
}

public function hasMultipleObjectStorages(): bool {
$objectStore = $this->config->getSystemValue('objectstore', []);
return isset($objectStore['default']);
}

/**
* @return ?array<string, ObjectStoreConfig|string>
* @throws InvalidObjectStoreConfigurationException
*/
private function getObjectStoreConfig(): ?array {
public function getObjectStoreConfigs(): ?array {
$objectStore = $this->config->getSystemValue('objectstore', null);
$objectStoreMultiBucket = $this->config->getSystemValue('objectstore_multibucket', null);

// new-style multibucket config uses the same 'objectstore' key but sets `'multibucket' => true`, transparently upgrade older style config
if ($objectStoreMultiBucket) {
$objectStoreMultiBucket['arguments']['multibucket'] = true;
return [
'default' => $this->validateObjectStoreConfig($objectStoreMultiBucket)
'default' => 'server1',
'server1' => $this->validateObjectStoreConfig($objectStoreMultiBucket),
'root' => 'server1',
];
} elseif ($objectStore) {
if (!isset($objectStore['default'])) {
$objectStore = [
'default' => $objectStore,
'default' => 'server1',
'root' => 'server1',
'server1' => $objectStore,
];
}
if (!isset($objectStore['root'])) {
$objectStore['root'] = 'default';
}

if (!is_string($objectStore['default'])) {
throw new InvalidObjectStoreConfigurationException('The \'default\' object storage configuration is required to be a reference to another configuration.');
}
return array_map($this->validateObjectStoreConfig(...), $objectStore);
} else {
return null;
}
}

/**
* @return ObjectStoreConfig
* @param array|string $config
* @return string|ObjectStoreConfig
*/
private function validateObjectStoreConfig(array $config) {
private function validateObjectStoreConfig(array|string $config): array|string {
if (is_string($config)) {
return $config;
}
if (!isset($config['class'])) {
throw new \Exception('No class configured for object store');
throw new InvalidObjectStoreConfigurationException('No class configured for object store');
}
if (!isset($config['arguments'])) {
$config['arguments'] = [];
}
$class = $config['class'];
$arguments = $config['arguments'];
if (!is_array($arguments)) {
throw new \Exception('Configured object store arguments are not an array');
throw new InvalidObjectStoreConfigurationException('Configured object store arguments are not an array');
}
if (!isset($arguments['multibucket'])) {
$arguments['multibucket'] = false;
}
if (!is_bool($arguments['multibucket'])) {
throw new \Exception('arguments.multibucket must be a boolean in object store configuration');
throw new InvalidObjectStoreConfigurationException('arguments.multibucket must be a boolean in object store configuration');
}

if (!is_string($class)) {
throw new \Exception('Configured class for object store is not a string');
throw new InvalidObjectStoreConfigurationException('Configured class for object store is not a string');
}

if (str_starts_with($class, 'OCA\\') && substr_count($class, '\\') >= 2) {
Expand All @@ -132,7 +177,7 @@ private function validateObjectStoreConfig(array $config) {
}

if (!is_a($class, IObjectStore::class, true)) {
throw new \Exception('Configured class for object store is not an object store');
throw new InvalidObjectStoreConfigurationException('Configured class for object store is not an object store');
}
return [
'class' => $class,
Expand All @@ -152,7 +197,7 @@ public function getBucketForUser(IUser $user, array $config): string {
$config['arguments']['bucket'] = '';
}
$mapper = new Mapper($user, $this->config);
$numBuckets = isset($config['arguments']['num_buckets']) ? $config['arguments']['num_buckets'] : 64;
$numBuckets = $config['arguments']['num_buckets'] ?? 64;
$bucket = $config['arguments']['bucket'] . $mapper->getBucket($numBuckets);

$this->config->setUserValue($user->getUID(), 'homeobjectstore', 'bucket', $bucket);
Expand All @@ -166,6 +211,15 @@ public function getSetBucketForUser(IUser $user): ?string {
}

public function getObjectStoreForUser(IUser $user): string {
return $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'objectstore', 'default');
if ($this->hasMultipleObjectStorages()) {
$value = $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'objectstore', null);
if ($value === null) {
$value = $this->resolveAlias('default');
$this->config->setUserValue($user->getUID(), 'homeobjectstore', 'objectstore', $value);
}
return $value;
} else {
return 'default';
}
}
}
Loading