Examples#
Introduction#
This page provides examples of custom checker plugins for Restore Verification. The examples demonstrate both simple file validation and more advanced application-level verification using external tools and checker lifecycle methods. They are intended as starting points for developing your own checker plugins.
Bacula MariaDB dump import checker#
This checker verifies a restored Bacula MariaDB database dump by importing it into a temporary database and checking whether expected Bacula Catalog data can be queried successfully.
<?php
namespace Bacularis\Common\Plugins;
use Bacularis\Common\Modules\BacularisCommonPluginBase;
use Bacularis\Common\Modules\IBacularisVerificationCheckPlugin;
use Bacularis\Common\Modules\RestoreDestinationCapability;
/**
* Check Bacula catalog MariaDB dump import.
*/
class BaculaMariaDBCheck extends BacularisCommonPluginBase implements IBacularisVerificationCheckPlugin
{
private const OPERATOR_EQUAL_TO = '==';
private const OPERATOR_DESC_EQUAL_TO = '== equal to';
private const OPERATOR_NOT_EQUAL_TO = '!=';
private const OPERATOR_DESC_NOT_EQUAL_TO = '!= not equal to';
private const SQL_CLI_PROGRAM = 'mariadb';
private const DATABASE = 'bacula_test_rv';
private const VERIFY_SQL = 'SELECT VersionId FROM Version LIMIT 1';
/**
* Get plugin name displayed in web interface.
*
* @return string plugin name
*/
public static function getName(): string
{
return 'Bacula MariaDB check';
}
/**
* Get plugin version.
*
* @return string plugin version
*/
public static function getVersion(): string
{
return '1.0.0';
}
/**
* Get plugin type.
*
* @return string plugin type
*/
public static function getType(): string
{
return 'verification';
}
/**
* Get plugin configuration parameters.
*
* @return array plugin parameters
*/
public static function getParameters(): array
{
return [];
}
/**
* Prepare MariaDB test database.
*
* @param array $test_config restore verification test configuration
*/
public static function setUp(array $test_config): void
{
$sql = sprintf(
'DROP DATABASE IF EXISTS `%s`; CREATE DATABASE `%s`;',
self::DATABASE,
self::DATABASE
);
$plugin = new self();
$plugin->execSQL($sql);
}
/**
* Clean up after MariaDB dump import test.
*
* @param array $test_config restore verification test configuration
*/
public static function tearDown(array $test_config): void
{
$plugin = new self();
$sql = sprintf('DROP DATABASE IF EXISTS `%s`;', self::DATABASE);
$plugin->execSQL($sql);
}
/**
* Main check command.
* It checks if the restored file is a Bacula MariaDB dump and if it can be imported.
*
* @param string $operator check operator
* @param mixed $current_value item value to check
* @param mixed $expected_value expected value
* @return array current value, expected value and check result: true on success, false otherwise
*/
public static function check(string $operator, $current_value, $expected_value): array
{
$path = (string) $current_value;
$expected = self::getExpectedResult($expected_value);
$ret = [
'result' => false,
'current' => '',
'expected' => $expected ? 'true' : 'false'
];
if (!self::isSQLDumpFile($path)) {
$ret['current'] = 'Not an SQL dump file';
return $ret;
}
$plugin = new self();
$import_result = $plugin->importDump($path);
if ($import_result['exitcode'] !== 0) {
$ret['current'] = self::getCommandOutput($import_result);
return $ret;
}
$query_result = $plugin->execSQL(self::VERIFY_SQL, self::DATABASE);
$version_id = self::getVersionId($query_result);
$ret['current'] = $version_id;
$valid = is_int($version_id) && $version_id > 0;
switch ($operator) {
case self::OPERATOR_EQUAL_TO: {
$ret['result'] = ($valid == $expected);
break;
}
case self::OPERATOR_NOT_EQUAL_TO: {
$ret['result'] = ($valid != $expected);
break;
}
}
return $ret;
}
/**
* Get main plugin attribute.
* Main attribute answers on question what the attribute is used
* in the check action.
* This is the first parameter defined in the verification rules.
*
* @return string main attribute
*/
public static function getAttribute(): string
{
return 'Bacula MariaDB dump import';
}
/**
* Get all supported operators by plugin.
*
* @return array operator list
*/
public static function getOperators(): array
{
return [
self::OPERATOR_EQUAL_TO => self::OPERATOR_DESC_EQUAL_TO,
self::OPERATOR_NOT_EQUAL_TO => self::OPERATOR_DESC_NOT_EQUAL_TO
];
}
/**
* Get possible values to select.
*
* @return array values to select or type
*/
public static function getValues(): array
{
return ['type' => 'list', 'values' => [true, false]];
}
/**
* Get checker capabilities.
* Capabilities define what data types is able to check and where
* it can be used.
*
* @return array check capabilities
*/
public static function getCapabilities(): array
{
return [
RestoreDestinationCapability::FILE_CHECK
];
}
/**
* Get checker requirements.
* Requirements define what this checker requires to correct working.
*
* @return array check requirements
*/
public static function getRequirements(): array
{
return [
self::SQL_CLI_PROGRAM
];
}
/**
* Execute SQL query using MariaDB client.
*
* @param string $sql SQL query
* @param string $database database name
* @return array command result
*/
private function execSQL(string $sql, string $database = ''): array
{
$cmd = [
self::SQL_CLI_PROGRAM,
'--batch',
'--skip-column-names'
];
if ($database !== '') {
$cmd[] = escapeshellarg($database);
}
$cmd[] = '--execute';
$cmd[] = escapeshellarg($sql);
$cmd[] = '2>&1';
return $this->execCommand($cmd);
}
/**
* Import SQL dump into the Bacula database.
*
* @param string $path dump file path
* @return array command result
*/
private function importDump(string $path): array
{
$cmd = [
self::SQL_CLI_PROGRAM,
escapeshellarg(self::DATABASE),
'<',
escapeshellarg($path),
'2>&1'
];
return $this->execCommand($cmd);
}
/**
* Check if the path points to SQL dump file.
*
* @param string $path dump file path
* @return bool true if it is an SQL dump file, false otherwise
*/
private static function isSQLDumpFile(string $path): bool
{
$extension = pathinfo($path, PATHINFO_EXTENSION);
$ret = (is_file($path) && strtolower($extension) === 'sql');
return $ret;
}
/**
* Get expected boolean result.
*
* @param mixed $expected_value expected value
* @return bool expected result
*/
private static function getExpectedResult($expected_value): bool
{
if (is_bool($expected_value)) {
return $expected_value;
}
return ((string) $expected_value === 'true' || (string) $expected_value === '1');
}
/**
* Get command output as text.
*
* @param array $result command result
* @return string command output
*/
private static function getCommandOutput(array $result): string
{
$output = '';
if (key_exists('output', $result)) {
$output = implode(PHP_EOL, $result['output']);
}
return $output;
}
/**
* Get Bacula VersionId value from MariaDB query output.
*
* @param array $result command result
* @return mixed VersionId integer or command output on error
*/
private static function getVersionId(array $result)
{
if ($result['exitcode'] != 0) {
return self::getCommandOutput($result);
}
if (!key_exists('output', $result) || count($result['output']) == 0) {
return '';
}
$value = trim($result['output'][0]);
if (is_numeric($value)) {
return (int) $value;
}
return $value;
}
}
How it works#
setUp()creates an empty temporary MariaDB database.check()imports the restored SQL dump.The checker queries the Bacula Version table.
The query result determines whether the verification condition passes.
tearDown()removes the temporary database.
Requirements#
This example assumes that the mariadb command-line client is already configured to connect to the test MariaDB server and that the account has sufficient privileges to create, drop, import, and query the temporary database.
The example assumes that the restored dump can be imported into the explicitly selected test database. Dumps containing their own database-selection or environment-specific statements may require additional handling.
Example Verification Rule#
This rule passes when the restored SQL dump can be successfully imported and the expected Bacula Catalog data can be queried.
Notes#
After the import, the checker queries the Bacula Version table. A valid VersionId confirms that the restored dump was imported and that expected Bacula Catalog data can be accessed.
This simplified example uses a fixed temporary database name. If multiple tests can run concurrently against the same MariaDB server, use a unique database name for each test to avoid conflicts.
DOCX file checker#
This checker performs basic integrity validation of restored DOCX files. It verifies that the file is recognized as a Microsoft Word/OOXML document and that its underlying ZIP archive is not corrupted.
<?php
namespace Bacularis\Common\Plugins;
use Bacularis\Common\Modules\BacularisCommonPluginBase;
use Bacularis\Common\Modules\IBacularisVerificationCheckPlugin;
use Bacularis\Common\Modules\RestoreDestinationCapability;
/**
* Check if DOCX file is valid and not corrupted.
*/
class DocxFileCheck extends BacularisCommonPluginBase implements IBacularisVerificationCheckPlugin
{
private const OPERATOR_EQUAL_TO = '==';
private const OPERATOR_DESC_EQUAL_TO = '== equal to';
private const OPERATOR_NOT_EQUAL_TO = '!=';
private const OPERATOR_DESC_NOT_EQUAL_TO = '!= not equal to';
private const FILE_PROGRAM = 'file';
private const UNZIP_PROGRAM = 'unzip';
/**
* Get plugin name displayed in web interface.
*
* @return string plugin name
*/
public static function getName(): string
{
return 'DOCX file check';
}
/**
* Get plugin type.
*
* @return string plugin type
*/
public static function getType(): string
{
return 'verification';
}
/**
* Get plugin version.
*
* @return string plugin version
*/
public static function getVersion(): string
{
return '1.0.0';
}
/**
* Get plugin configuration parameters.
*
* @return array plugin parameters
*/
public static function getParameters(): array
{
return [];
}
/**
* Main check command.
* It checks if DOCX file header is valid and if archive test succeeds.
*
* @param string $operator check operator
* @param mixed $current_value item value to check
* @param mixed $expected_value expected value
* @return array current value, expected value and check result: true on success, false otherwise
*/
public static function check(string $operator, $current_value, $expected_value): array
{
$path = (string) $current_value;
$expected = self::getExpectedResult($expected_value);
$ret = [
'result' => false,
'current' => '',
'expected' => $expected ? 'true' : 'false'
];
if (!is_file($path)) {
$ret['current'] = 'File does not exist';
return $ret;
}
$plugin = new self();
$file_result = $plugin->checkFileHeader($path);
$unzip_result = $plugin->checkZipArchive($path);
$file_output = self::getCommandOutput($file_result);
$unzip_output = self::getCommandOutput($unzip_result);
$header_valid = self::isDocxHeader($file_output);
$archive_valid = ($unzip_result['exitcode'] === 0);
$value = ($file_result['exitcode'] === 0 && $header_valid && $archive_valid);
$ret['current'] = self::getCurrentValue($value, $file_output, $unzip_output);
switch ($operator) {
case self::OPERATOR_EQUAL_TO: {
$ret['result'] = ($value == $expected);
break;
}
case self::OPERATOR_NOT_EQUAL_TO: {
$ret['result'] = ($value != $expected);
break;
}
}
return $ret;
}
/**
* Get main plugin attribute.
* Main attribute answers on question what the attribute is used
* in the check action.
* This is the first parameter defined in the verification rules.
*
* @return string main attribute
*/
public static function getAttribute(): string
{
return 'DOCX file valid';
}
/**
* Get all supported operators by plugin.
*
* @return array operator list
*/
public static function getOperators(): array
{
return [
self::OPERATOR_EQUAL_TO => self::OPERATOR_DESC_EQUAL_TO,
self::OPERATOR_NOT_EQUAL_TO => self::OPERATOR_DESC_NOT_EQUAL_TO
];
}
/**
* Get possible values to select.
*
* @return array values to select or type
*/
public static function getValues(): array
{
return ['type' => 'list', 'values' => [true, false]];
}
/**
* Get checker capabilities.
* Capabilities define what data types is able to check and where
* it can be used.
*
* @return array check capabilities
*/
public static function getCapabilities(): array
{
return [
RestoreDestinationCapability::FILE_CHECK
];
}
/**
* Get checker requirements.
* Requirements define what this checker requires to correct working.
*
* @return array check requirements
*/
public static function getRequirements(): array
{
return [
self::FILE_PROGRAM,
self::UNZIP_PROGRAM
];
}
/**
* Check DOCX file header.
*
* @param string $path file path
* @return array command result
*/
private function checkFileHeader(string $path): array
{
$cmd = [
self::FILE_PROGRAM,
'-b',
escapeshellarg($path),
'2>&1'
];
return $this->execCommand($cmd);
}
/**
* Test DOCX ZIP archive.
*
* @param string $path file path
* @return array command result
*/
private function checkZipArchive(string $path): array
{
$cmd = [
self::UNZIP_PROGRAM,
'-t',
escapeshellarg($path),
'2>&1'
];
return $this->execCommand($cmd);
}
/**
* Check if file command output describes Word DOCX file.
*
* @param string $output file command output
* @return bool true if output describes DOCX file, false otherwise
*/
private static function isDocxHeader(string $output): bool
{
$header = strtolower($output);
$is_word = (strpos($header, 'microsoft word') !== false);
$is_ooxml = (strpos($header, 'microsoft ooxml') !== false);
$is_docx = (strpos($header, 'wordprocessingml') !== false);
return ($is_word || $is_ooxml || $is_docx);
}
/**
* Get expected boolean result.
*
* @param mixed $expected_value expected value
* @return bool expected result
*/
private static function getExpectedResult($expected_value): bool
{
if (is_bool($expected_value)) {
return $expected_value;
}
return ((string) $expected_value === 'true' || (string) $expected_value === '1');
}
/**
* Get current check value.
*
* @param bool $value check result
* @param string $file_output file command output
* @param string $unzip_output unzip command output
* @return string current value
*/
private static function getCurrentValue(bool $value, string $file_output, string $unzip_output): string
{
$current = $value ? 'true' : 'false';
$file_result = sprintf('file: %s', $file_output);
$unzip_result = sprintf('unzip: %s', $unzip_output);
return implode('; ', [$current, $file_result, $unzip_result]);
}
/**
* Get command output as text.
*
* @param array $result command result
* @return string command output
*/
private static function getCommandOutput(array $result): string
{
$output = '';
if (key_exists('output', $result)) {
$output = implode(PHP_EOL, $result['output']);
}
return $output;
}
}
How it works#
The checker verifies that the restored path is a file.
The
fileutility identifies the document format.unzip -tverifies the integrity of the underlying ZIP archive.Both checks must succeed for the DOCX file to be considered valid.
Requirements#
This checker requires the file and unzip command-line utilities on the Restore Destination.
Example Verification Rule#
This rule passes when the restored file is recognized as a DOCX document and its underlying ZIP archive passes the integrity test.
Notes#
The exact output of the file utility may vary between operating systems and versions of the magic database. A production checker may need to account for additional output variants.