Skip to content
This repository has been archived by the owner on Feb 20, 2024. It is now read-only.

Commit

Permalink
Restore command (#110)
Browse files Browse the repository at this point in the history
* Added command that lets you restore the database from previous backup.

* Travis test

* Bugfix with filename

* Added dropbox support for download

* Bugfix

* Optional

* Optional

* Throw exception if some parameters are not set
  • Loading branch information
jongotlin authored and Nyholm committed Jan 31, 2017
1 parent 01ca897 commit c5db8bc
Show file tree
Hide file tree
Showing 33 changed files with 1,035 additions and 24 deletions.
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ language: php
sudo: false

php:
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm

before_script: composer install --dev --prefer-source
before_script: composer install --prefer-source

script: phpunit --debug --coverage-text

Expand Down
12 changes: 12 additions & 0 deletions Client/DownloadableClientInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
namespace Dizda\CloudBackupBundle\Client;

interface DownloadableClientInterface
{
/**
* Download lastest backup file.
*
* @return \SplFileInfo
*/
public function download();
}
55 changes: 52 additions & 3 deletions Client/DropboxSdkClient.php
Original file line number Diff line number Diff line change
@@ -1,26 +1,44 @@
<?php
namespace Dizda\CloudBackupBundle\Client;

use Dizda\CloudBackupBundle\Exception\InvalidConfigurationException;
use Dizda\CloudBackupBundle\Exception\RestoringNotAvailableException;
use Dizda\CloudBackupBundle\Exception\UploadException;
use \Dropbox as Dropbox;
use Symfony\Component\Filesystem\Filesystem;

/**
* Class DropboxSdkClient.
*
* @author David Fuertes
*/
class DropboxSdkClient implements ClientInterface
class DropboxSdkClient implements ClientInterface, DownloadableClientInterface
{
/**
* @var string
*/
private $access_token;

/**
* @param array $params user
* @var string
*/
private $restoreFolder;

/**
* @var Filesystem
*/
public function __construct($params)
private $localFilesystem;

/**
*
* @param array $params
* @param $restoreFolder
* @param Filesystem $localFilesystem
*/
public function __construct($params, $restoreFolder = null, Filesystem $localFilesystem = null)
{
$this->restoreFolder = $restoreFolder;
$this->localFilesystem = $localFilesystem;
$params = $params['dropbox_sdk'];
$this->access_token = $params['access_token'];
$this->remotePath = $params['remote_path'];
Expand All @@ -46,6 +64,37 @@ public function upload($archive)
fclose($fp);
}

public function download()
{
if (!$this->restoreFolder) {
throw InvalidConfigurationException::create('$restoreFolder');
}
$pathError = Dropbox\Path::findErrorNonRoot($this->remotePath);
if ($pathError !== null) {
throw new UploadException(sprintf('Invalid path "%s".', $this->remotePath));
}
$client = new Dropbox\Client($this->access_token, 'CloudBackupBundle');
$entry = $client->getMetadataWithChildren($this->remotePath);
if (!$entry['is_dir']) {
throw RestoringNotAvailableException::create();
}

// Fetch the latest file
$file = end($entry['contents']);
$fileName = substr($file['path'], 1+strrpos($file['path'], '/'));
$stream = fopen('php://temp', 'r+');
$client->getFile($file['path'], $stream);
fseek($stream, 0);
$content = stream_get_contents($stream);

$splFile = new \SplFileInfo($this->restoreFolder . $fileName);

$this->localFilesystem->dumpFile($splFile->getPathname(), $content);

return $splFile;
}


/**
* {@inheritdoc}
*/
Expand Down
57 changes: 55 additions & 2 deletions Client/GaufretteClient.php
Original file line number Diff line number Diff line change
@@ -1,19 +1,43 @@
<?php
namespace Dizda\CloudBackupBundle\Client;

use Symfony\Component\Console\Output\ConsoleOutput;
use Dizda\CloudBackupBundle\Exception\InvalidConfigurationException;
use Gaufrette\Filesystem;
use Symfony\Component\Filesystem\Filesystem as LocalFilesystem;

/**
* Class GaufretteClient
* Client for Gaufrette drivers.
*
* @author Jonathan Dizdarevic <dizda@dizda.fr>
*/
class GaufretteClient implements ClientInterface
class GaufretteClient implements ClientInterface, DownloadableClientInterface
{
/**
* @var Filesystem[]
*/
private $filesystems;

/**
* @var string
*/
private $restoreFolder;

/**
* @var LocalFilesystem
*/
private $localFilesystem;

/**
* @param string $restoreFolder
* @param LocalFilesystem $localFilesystem
*/
public function __construct($restoreFolder = null, LocalFilesystem $localFilesystem = null)
{
$this->restoreFolder = $restoreFolder;
$this->localFilesystem = $localFilesystem;
}

/**
* {@inheritdoc}
*/
Expand All @@ -35,11 +59,40 @@ public function addFilesystem(Filesystem $filesystem)
$this->filesystems[] = $filesystem;
}

/**
* @return Filesystem
*/
private function getFirstFilesystem()
{
return $this->filesystems[0];
}

/**
* {@inheritdoc}
*/
public function getName()
{
return 'Gaufrette';
}

/**
* {@inheritdoc}
*/
public function download()
{
if (!$this->restoreFolder) {
throw InvalidConfigurationException::create('$restoreFolder');
}
$fileSystem = $this->getFirstFilesystem();

$files = $fileSystem->keys();
$fileName = end($files);

$content = $fileSystem->get($fileName)->getContent();
$splFile = new \SplFileInfo($this->restoreFolder . $fileName);

$this->localFilesystem->dumpFile($splFile->getPathname(), $content);

return $splFile;
}
}
84 changes: 84 additions & 0 deletions Command/RestoreCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

namespace Dizda\CloudBackupBundle\Command;

use Dizda\CloudBackupBundle\Manager\RestoreManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class RestoreCommand extends Command
{
const RETURN_STATUS_SUCCESS = 0;
const RETURN_STATUS_NOT_AVAILABLE = 1;
const RETURN_STATUS_MISSING_FORCE_FLAG = 2;
const RETURN_STATUS_EXCEPTION_OCCURRED = 3;

/**
* @var bool
*/
protected $doRestore;

/**
* @var RestoreManager
*/
protected $restoreManager;

/**
* @param boolean $doRestore
* @param RestoreManager $restoreManager
*/
public function __construct($doRestore, RestoreManager $restoreManager)
{
$this->doRestore = $doRestore;
$this->restoreManager = $restoreManager;

parent::__construct();
}

/**
* Configure the command.
*/
protected function configure()
{
$this
->setName('dizda:backup:restore')
->setDescription('Download latest backup, uncompress and restore.')
->addOption('force', null, InputOption::VALUE_NONE)
;
}

/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if (!$this->doRestore) {
$output->writeln('<error>Restore is not available. Enable by setting dizda_cloud_backup.restore: true in config.yml.</error>');

return self::RETURN_STATUS_NOT_AVAILABLE;
}

if (!$input->getOption('force')) {
$output->writeln('<error>Run command with --force to execute.</error>');

return self::RETURN_STATUS_MISSING_FORCE_FLAG;
}

$output->writeln('Restoring backup started');

if ($this->restoreManager->execute()) {
$output->writeln('Restoring backup completed');

return self::RETURN_STATUS_SUCCESS;
} else {
$output->writeln('<error>Something went wrong</error>');

return self::RETURN_STATUS_EXCEPTION_OCCURRED;
}
}
}
52 changes: 48 additions & 4 deletions Database/MySQL.php
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<?php
namespace Dizda\CloudBackupBundle\Database;

use Dizda\CloudBackupBundle\Exception\InvalidConfigurationException;
use Symfony\Component\Process\ProcessUtils;

/**
* Class MySQL.
*
* @author Jonathan Dizdarevic <dizda@dizda.fr>
*/
class MySQL extends BaseDatabase
class MySQL extends BaseDatabase implements RestorableDatabaseInterface
{
const DB_PATH = 'mysql';
const CONFIGURATION_FILE_NAME = 'mysql.cnf';
Expand All @@ -20,14 +21,20 @@ class MySQL extends BaseDatabase
private $params;

/**
* DB Auth.
*
* @var string
*/
private $restoreFolder;

/**
* @param array $params
* @param string $basePath
* @param string $restoreFolder
*/
public function __construct($params, $basePath)
public function __construct($params, $basePath, $restoreFolder = null)
{
parent::__construct($basePath);

$this->restoreFolder = $restoreFolder;
$this->params = $params['mysql'];
}

Expand Down Expand Up @@ -135,6 +142,14 @@ public function dump()
$this->removeConfigurationFile();
}

/**
* {@inheritdoc}
*/
public function restore()
{
$this->execute($this->getRestoreCommand());
}

/**
* {@inheritdoc}
*/
Expand All @@ -148,6 +163,35 @@ protected function getCommand()
);
}

/**
* {@inheritdoc}
*/
protected function getRestoreCommand()
{
if (!$this->restoreFolder) {
throw InvalidConfigurationException::create('$restoreFolder');
}

$restoreAuth = '';
if ($this->params['db_user']) {
$restoreAuth = sprintf('-u%s', $this->params['db_user']);

if ($this->params['db_password']) {
$restoreAuth = $restoreAuth . sprintf(" --password=\"%s\"", $this->params['db_password']);
}
}

$this->prepareFileName();

$command = sprintf('mysql %s %s < %s',
$restoreAuth,
$this->params['database'],
ProcessUtils::escapeArgument(sprintf('%smysql/%s', $this->restoreFolder, $this->fileName))
);

return $command;
}

/**
* {@inheritdoc}
*/
Expand Down
11 changes: 11 additions & 0 deletions Database/RestorableDatabaseInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Dizda\CloudBackupBundle\Database;

interface RestorableDatabaseInterface
{
/**
* Restore the database with a previous dump
*/
public function restore();
}
1 change: 1 addition & 0 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ public function getConfigTreeBuilder()
->end()
->end()
->end()
->booleanNode('restore')->defaultFalse()->end()
->end();

return $treeBuilder;
Expand Down
Loading

0 comments on commit c5db8bc

Please sign in to comment.