Skip to content

Commit

Permalink
Implement the logic to backup and restore multiple configs (#106)
Browse files Browse the repository at this point in the history
Co-authored-by: Sergey Nikolaev <[email protected]>
  • Loading branch information
donhardman and sanikolaev authored Feb 22, 2024
1 parent 2164d3b commit d6cd26d
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 39 deletions.
2 changes: 1 addition & 1 deletion src/Lib/ManticoreBackup.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ protected static function store(ManticoreClient $client, FileStorage $storage, a
println(LogLevel::Info, 'Backing up config files...');
$isOk = $storage->copyPaths(
[
$client->getConfig()->path,
...array_map(fn ($v) => $v->path, $client->getConfigs()),
$client->getConfig()->schemaPath,
], $destination['config'], true
);
Expand Down
44 changes: 33 additions & 11 deletions src/Lib/ManticoreClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,16 @@
class ManticoreClient {
const API_PATH = '/sql?mode=raw';

protected ManticoreConfig $config;
/**
* @var array<ManticoreConfig>
*/
protected array $configs;

public function __construct(ManticoreConfig $config) {
$this->config = $config;
/**
* @param array<ManticoreConfig> $configs [description]
*/
public function __construct(array $configs) {
$this->configs = $configs;

$versions = $this->getVersions();
$verNum = strtok($versions['manticore'], ' ');
Expand Down Expand Up @@ -59,9 +65,9 @@ public function __construct(ManticoreConfig $config) {

// Validate config path or fail
$configPath = $this->getConfigPath();
if (!OS::isSamePath($configPath, $this->config->path)) {
if (!OS::isSamePath($configPath, $this->getConfig()->path)) {
throw new \RuntimeException(
"Configs mismatched: '{$this->config->path} <> {$configPath}"
"Configs mismatched: '{$this->getConfig()->path} <> {$configPath}"
. ', make sure the instance you are backing up is using the provided config'
);
}
Expand All @@ -74,18 +80,33 @@ public function __construct(ManticoreConfig $config) {
* Structure with initialized config
*/
public function getConfig(): ManticoreConfig {
return $this->config;
// This should never happen
if (!isset($this->configs[0])) {
throw new \RuntimeException('Failed to fetch config due to nothing presents');
}
return $this->configs[0];
}

/**
* Get all configs defined for backup
* @return array<ManticoreConfig>
*/
public function getConfigs(): array {
return $this->configs;
}

/**
* Helper function that we will use for first init of client and config
*
* @param string $configPath
* @param array<string> $configPaths
* @return self
*/
public static function init(string $configPath): self {
$config = new ManticoreConfig($configPath);
return new ManticoreClient($config);
public static function init(array $configPaths): self {
$configs = [];
foreach ($configPaths as $configPath) {
$configs[] = new ManticoreConfig($configPath);
}
return new ManticoreClient($configs);
}

/**
Expand Down Expand Up @@ -247,9 +268,10 @@ public function execute(string $query): array {
],
];
$context = stream_context_create($opts);
$config = $this->getConfig();
try {
$result = file_get_contents(
$this->config->proto . '://' . $this->config->host . ':' . $this->config->port . static::API_PATH,
$config->proto . '://' . $config->host . ':' . $config->port . static::API_PATH,
false,
$context
);
Expand Down
21 changes: 17 additions & 4 deletions src/Lib/Searchd.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ class Searchd {
public static ?string $cmd;

/**
* @return string
* @return array<string>
*/
public static function getConfigPath(): string {
public static function getConfigPaths(): array {
// First check env and if we have there, return config file from there
$envConfig = getenv('MANTICORE_CONFIG');
if ($envConfig) {
return $envConfig;
return array_map(trim(...), explode('|', $envConfig));
}

$output = shell_exec(static::getCmd() . ' --status');
Expand All @@ -38,7 +38,20 @@ public static function getConfigPath(): string {
throw new InvalidPathException('Failed to find searchd config from command line');
}

return backup_realpath($m[1]);
return [backup_realpath($m[1])];
}

/**
* Get actual config path from all ocnfigs
* @return string
*/
public static function getConfigPath(): string {
$configs = static::getConfigPaths();
if (!isset($configs[0])) {
throw new \RuntimeException('Failed to find actual config from the provided paths');
}

return $configs[0];
}

/**
Expand Down
30 changes: 23 additions & 7 deletions src/func.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@
*
* @param array<string,string> $args
* Parsed args with getopt
* @return array{config:string,backup-dir:?string,compress:bool,tables:array<string>,restore:string|false,disable-telemetry:bool,force:bool}
* @return array{configs:array<string>,backup-dir:?string,compress:bool,tables:array<string>,restore:string|false,disable-telemetry:bool,force:bool}
* Options that we can use for access with predefined keys: config, backup-dir, all, tables
*/
function validate_args(array $args): array {
$options = [
'config' => $args['config'] ?? ($args['c'] ?? (isset($args['restore']) ? '' : Searchd::getConfigPath())),
'configs' => validate_get_configs($args),
'backup-dir' => $args['backup-dir'] ?? null,
'compress' => isset($args['compress']),
'tables' => array_filter(array_map('trim', explode(',', $args['tables'] ?? ''))),
Expand All @@ -36,9 +36,14 @@ function validate_args(array $args): array {

// Validate arguments
if (!isset($args['restore'])) {
$options['config'] = backup_realpath($options['config']);
if (!is_file($options['config']) || !is_readable($options['config'])) {
throw new InvalidArgumentException('Failed to find passed config: ' . $options['config']);
$options['configs'] = array_map(
fn ($v) => backup_realpath($v),
$options['configs']
);
foreach ($options['configs'] as $n => $config) {
if (!is_file($config) || !is_readable($config)) {
throw new InvalidArgumentException("Failed to find passed config[{$n}]: {$config}");
}
}
}

Expand All @@ -61,6 +66,14 @@ function validate_args(array $args): array {
return $options;
}

/**
* @param array<string,string> $args
* @return array<string>
*/
function validate_get_configs(array $args): array {
return (array)($args['config'] ?? ($args['c'] ?? (isset($args['restore']) ? '' : Searchd::getConfigPaths())));
}

/**
* Little helper to conver bytes to human readable size
*
Expand Down Expand Up @@ -91,7 +104,7 @@ function format_bytes(int $bytes, int $precision = 3): string {
function get_input_args(): array {
$args = getopt(
'', [
'help', 'config:', 'tables:', 'backup-dir:',
'help', 'config::', 'tables:', 'backup-dir:',
'compress', 'restore::', 'unlock', 'version', 'disable-telemetry',
'force',
]
Expand Down Expand Up @@ -195,7 +208,10 @@ function show_help(): void {
. $nl
. " Path to Manticore config. This is optional and in case it's not passed$nl"
. " we use a default one for your operating system. It's used to get the host$nl"
. " and port to talk with the Manticore daemon.$nl$nl"
. " and port to talk with the Manticore daemon.$nl"
. " You can use --config path1 --config path2 ... --config pathN$nl"
. " to include all of the provided paths in the backup, but only$nl"
. " the first one will be used for communication with the daemon.$nl$nl"
. colored('--tables', TextColor::LightGreen)
. '='
. colored('table1,table2,...', TextColor::LightBlue)
Expand Down
7 changes: 4 additions & 3 deletions src/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@

// OK, now gather all options in an array with default values
$options = validate_args($args); // @phpstan-ignore-line
echo 'Manticore config file: ' . $options['config'] . PHP_EOL
$config = $options['configs'][0] ?? '';
echo 'Manticore config file: ' . $config . PHP_EOL
. (
isset($args['restore'])
? ''
Expand All @@ -80,7 +81,7 @@

switch (true) {
case isset($args['unlock']): // unlock
$client = ManticoreClient::init($options['config']);
$client = ManticoreClient::init($options['configs']);
$client->unfreezeAll();
break;

Expand Down Expand Up @@ -139,7 +140,7 @@
break;

default: // backup
$client = ManticoreClient::init($options['config']);
$client = ManticoreClient::init($options['configs']);

$storage = new FileStorage($options['backup-dir'], $options['compress']);

Expand Down
2 changes: 1 addition & 1 deletion test/ManticoreBackupRestoreTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public static function setUpBeforeClass(): void {
SearchdTestCase::setUpBeforeClass();

static::$config = new ManticoreConfig(Searchd::getConfigPath());
$client = new ManticoreClient(static::$config);
$client = new ManticoreClient([static::$config]);

$fileStorage = new FileStorage(static::$backupDir);
ManticoreBackup::run('store', [$client, $fileStorage]);
Expand Down
20 changes: 10 additions & 10 deletions test/ManticoreBackupTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
class ManticoreBackupTest extends SearchdTestCase {
public function testStoreAllTables(): void {
[$config, $storage, $backupDir] = $this->initTestEnv();
$client = new ManticoreClient($config);
$client = new ManticoreClient([$config]);

// Backup of all tables
ManticoreBackup::run('store', [$client, $storage, []]);
Expand All @@ -45,7 +45,7 @@ public function testStoreAllTablesToSymlinkPath(): void {
rename($backupDir, $realPath);
shell_exec("ln -s '$realPath' '$backupDir'");

$client = new ManticoreClient($config);
$client = new ManticoreClient([$config]);

// Backup of all tables
ManticoreBackup::run('store', [$client, $storage, []]);
Expand All @@ -66,15 +66,15 @@ public function testStoreAllTablesToSymlinkPath(): void {

public function testStoreOnlyTwoTables(): void {
[$config, $storage, $backupDir] = $this->initTestEnv();
$client = new ManticoreClient($config);
$client = new ManticoreClient([$config]);

ManticoreBackup::run('store', [$client, $storage, ['movie', 'people']]);
$this->assertBackupIsOK($client, $backupDir, ['movie' => 'rt', 'people' => 'rt']);
}

public function testStoreOnlyOneIndex(): void {
[$config, $storage, $backupDir] = $this->initTestEnv();
$client = new ManticoreClient($config);
$client = new ManticoreClient([$config]);

// Backup only one
ManticoreBackup::run('store', [$client, $storage, ['people']]);
Expand All @@ -83,15 +83,15 @@ public function testStoreOnlyOneIndex(): void {

public function testStoreUnexistingIndexOnly(): void {
[$config, $storage] = $this->initTestEnv();
$client = new ManticoreClient($config);
$client = new ManticoreClient([$config]);

$this->expectException(InvalidArgumentException::class);
ManticoreBackup::run('store', [$client, $storage, ['unknown']]);
}

public function testStoreExistingAndUnexistingTablesTogether(): void {
[$config, $storage] = $this->initTestEnv();
$client = new ManticoreClient($config);
$client = new ManticoreClient([$config]);

$this->expectException(InvalidArgumentException::class);
ManticoreBackup::run('store', [$client, $storage, ['people', 'unknown']]);
Expand All @@ -101,7 +101,7 @@ public function testStoreExistingAndUnexistingTablesTogether(): void {
// https://github.com/alpinelinux/docker-alpine/issues/156
// public function testStoreFailsInCaseNoPermissionsToWriteTargetDir(): void {
// [$config, $storage, $backupDir] = $this->initTestEnv();
// $client = new ManticoreClient($config);
// $client = new ManticoreClient([$config]);

// // Create read only dir and modify it in FileStorage
// $roBackupDir = '/mnt' . $backupDir . '-ro';
Expand All @@ -115,7 +115,7 @@ public function testStoreExistingAndUnexistingTablesTogether(): void {

public function testStoreAbortedOnSignalCaught(): void {
[$config, $storage] = $this->initTestEnv();
$client = new ManticoreMockedClient($config);
$client = new ManticoreMockedClient([$config]);
$client->setTimeout(1);
$client->setTimeoutFn(
function () use ($client, $storage): bool {
Expand Down Expand Up @@ -149,7 +149,7 @@ function () use ($client, $storage): bool {

public function testStoreAbortedOnPermissionChanges(): void {
[$config, $storage] = $this->initTestEnv();
$client = new ManticoreMockedClient($config);
$client = new ManticoreMockedClient([$config]);

$client->setTimeout(1);
$client->setTimeoutFn(
Expand Down Expand Up @@ -202,7 +202,7 @@ public function initTestEnv(): array {
);

return [
new ManticoreConfig($options['config']),
new ManticoreConfig($options['configs'][0]),
new FileStorage($options['backup-dir']),
$backupDir,
];
Expand Down
2 changes: 1 addition & 1 deletion test/ManticoreClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ManticoreClientTest extends SearchdTestCase {
public function setUp(): void {

$config = new ManticoreConfig(Searchd::getConfigPath());
$this->client = new ManticoreClient($config);
$this->client = new ManticoreClient([$config]);
}

public function testGetVersions(): void {
Expand Down
2 changes: 1 addition & 1 deletion test/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
$configPath = Searchd::getConfigPath();

$config = new ManticoreConfig($configPath);
$client = new ManticoreClient($config);
$client = new ManticoreClient([$config]);

// people table
$client->execute('DROP TABLE IF EXISTS people');
Expand Down
3 changes: 3 additions & 0 deletions tests/help.rec
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ times.
Path to Manticore config. This is optional and in case it's not passed
we use a default one for your operating system. It's used to get the host
and port to talk with the Manticore daemon.
You can use --config path1 --config path2 ... --config pathN
to include all of the provided paths in the backup, but only
the first one will be used for communication with the daemon.
--tables=table1,table2,...
Semicolon-separated list of tables that you want to backup.
If you want to backup all, just skip this argument. All the provided tables
Expand Down

0 comments on commit d6cd26d

Please sign in to comment.