feat: multi object store rename command

Signed-off-by: Robin Appelman <robin@icewind.nl>
pull/52786/head
Robin Appelman 4 months ago
parent b3c53c7436
commit 4990d75007
  1. 1
      apps/files/appinfo/info.xml
  2. 1
      apps/files/composer/composer/autoload_classmap.php
  3. 1
      apps/files/composer/composer/autoload_static.php
  4. 108
      apps/files/lib/Command/Object/Multi/Rename.php

@ -54,6 +54,7 @@
<command>OCA\Files\Command\Object\ListObject</command>
<command>OCA\Files\Command\Object\Orphans</command>
<command>OCA\Files\Command\Object\Multi\Users</command>
<command>OCA\Files\Command\Object\Multi\Rename</command>
<command>OCA\Files\Command\WindowsCompatibleFilenames</command>
</commands>

@ -37,6 +37,7 @@ return array(
'OCA\\Files\\Command\\Object\\Get' => $baseDir . '/../lib/Command/Object/Get.php',
'OCA\\Files\\Command\\Object\\Info' => $baseDir . '/../lib/Command/Object/Info.php',
'OCA\\Files\\Command\\Object\\ListObject' => $baseDir . '/../lib/Command/Object/ListObject.php',
'OCA\\Files\\Command\\Object\\Multi\\Rename' => $baseDir . '/../lib/Command/Object/Multi/Rename.php',
'OCA\\Files\\Command\\Object\\Multi\\Users' => $baseDir . '/../lib/Command/Object/Multi/Users.php',
'OCA\\Files\\Command\\Object\\ObjectUtil' => $baseDir . '/../lib/Command/Object/ObjectUtil.php',
'OCA\\Files\\Command\\Object\\Orphans' => $baseDir . '/../lib/Command/Object/Orphans.php',

@ -52,6 +52,7 @@ class ComposerStaticInitFiles
'OCA\\Files\\Command\\Object\\Get' => __DIR__ . '/..' . '/../lib/Command/Object/Get.php',
'OCA\\Files\\Command\\Object\\Info' => __DIR__ . '/..' . '/../lib/Command/Object/Info.php',
'OCA\\Files\\Command\\Object\\ListObject' => __DIR__ . '/..' . '/../lib/Command/Object/ListObject.php',
'OCA\\Files\\Command\\Object\\Multi\\Rename' => __DIR__ . '/..' . '/../lib/Command/Object/Multi/Rename.php',
'OCA\\Files\\Command\\Object\\Multi\\Users' => __DIR__ . '/..' . '/../lib/Command/Object/Multi/Users.php',
'OCA\\Files\\Command\\Object\\ObjectUtil' => __DIR__ . '/..' . '/../lib/Command/Object/ObjectUtil.php',
'OCA\\Files\\Command\\Object\\Orphans' => __DIR__ . '/..' . '/../lib/Command/Object/Orphans.php',

@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Files\Command\Object\Multi;
use OC\Core\Command\Base;
use OC\Files\ObjectStore\PrimaryObjectStoreConfig;
use OCP\IConfig;
use OCP\IDBConnection;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class Rename extends Base {
public function __construct(
private readonly IDBConnection $connection,
private readonly PrimaryObjectStoreConfig $objectStoreConfig,
private readonly IConfig $config,
) {
parent::__construct();
}
protected function configure(): void {
parent::configure();
$this
->setName('files:object:multi:rename-config')
->setDescription('Rename an object store configuration and move all users over to the new configuration,')
->addArgument('source', InputArgument::REQUIRED, 'Object store configuration to rename')
->addArgument('target', InputArgument::REQUIRED, 'New name for the object store configuration');
}
public function execute(InputInterface $input, OutputInterface $output): int {
$source = $input->getArgument('source');
$target = $input->getArgument('target');
$configs = $this->objectStoreConfig->getObjectStoreConfigs();
if (!isset($configs[$source])) {
$output->writeln('<error>Unknown object store configuration: ' . $source . '</error>');
return 1;
}
if ($source === 'root') {
$output->writeln('<error>Renaming the root configuration is not supported.</error>');
return 1;
}
if ($source === 'default') {
$output->writeln('<error>Renaming the default configuration is not supported.</error>');
return 1;
}
if (!isset($configs[$target])) {
$output->writeln('<comment>Target object store configuration ' . $target . ' doesn\'t exist yet.</comment>');
$output->writeln('The target configuration can be created automatically.');
$output->writeln('However, as this depends on modifying the config.php, this only works as long as the instance runs on a single node or all nodes in a clustered setup have a shared config file (such as from a shared network mount).');
$output->writeln('If the different nodes have a separate copy of the config.php file, the automatic object store configuration creation will lead to the configuration going out of sync.');
$output->writeln('If these requirements are not met, you can manually create the target object store configuration in each node\'s configuration before running the command.');
$output->writeln('');
$output->writeln('<error>Failure to check these requirements will lead to data loss for users.</error>');
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Automatically create target object store configuration? [y/N] ', false);
if ($helper->ask($input, $output, $question)) {
$configs[$target] = $configs[$source];
// update all aliases
foreach ($configs as &$config) {
if ($config === $source) {
$config = $target;
}
}
$this->config->setSystemValue('objectstore', $configs);
} else {
return 0;
}
} elseif (($configs[$source] !== $configs[$target]) || $configs[$source] !== $target) {
$output->writeln('<error>Source and target configuration differ.</error>');
$output->writeln('');
$output->writeln('To ensure proper migration of users, the source and target configuration must be the same to ensure that the objects for the moved users exist on the target configuration.');
$output->writeln('The usual migration process consists of creating a clone of the old configuration, moving the users from the old configuration to the new one, and then adjust the old configuration that is longer used.');
return 1;
}
$query = $this->connection->getQueryBuilder();
$query->update('preferences')
->set('configvalue', $query->createNamedParameter($target))
->where($query->expr()->eq('appid', $query->createNamedParameter('homeobjectstore')))
->andWhere($query->expr()->eq('configkey', $query->createNamedParameter('objectstore')))
->andWhere($query->expr()->eq('configvalue', $query->createNamedParameter($source)));
$count = $query->executeStatement();
if ($count > 0) {
$output->writeln('Moved <info>' . $count . '</info> users');
} else {
$output->writeln('No users moved');
}
return 0;
}
}
Loading…
Cancel
Save