├── tests ├── .gitkeep ├── Commands │ ├── resources │ │ ├── EmptyFolder │ │ │ └── .gitkeep │ │ └── NonEmptyFolder │ │ │ ├── hello.sql │ │ │ └── world.sql │ ├── RestoreCommandTest.php │ └── BackupCommandTest.php ├── ConsoleTest.php ├── Databases │ ├── SqliteDatabaseTest.php │ ├── MySQLDatabaseTest.php │ └── PostgresDatabaseTest.php └── DatabaseBuilderTest.php ├── src ├── config │ ├── .gitkeep │ └── config.php ├── Console.php ├── Databases │ ├── DatabaseInterface.php │ ├── SqliteDatabase.php │ ├── PostgresDatabase.php │ └── MySQLDatabase.php ├── Commands │ ├── BaseCommand.php │ ├── RestoreCommand.php │ └── BackupCommand.php ├── DatabaseBuilder.php ├── LaravelDbBackupServiceProvider.php └── ConsoleColors.php ├── .travis.yml ├── phpunit.xml ├── composer.json ├── .gitignore └── README.md /tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/config/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/Commands/resources/EmptyFolder/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - hhvm 8 | 9 | before_script: 10 | - travis_retry composer self-update 11 | - travis_retry composer install --prefer-source --no-interaction --dev 12 | 13 | script: phpunit 14 | -------------------------------------------------------------------------------- /src/config/config.php: -------------------------------------------------------------------------------- 1 | storage_path() . '/dumps/', 6 | 7 | 'mysql' => array( 8 | 'dump_command_path' => '', 9 | 'restore_command_path' => '', 10 | ), 11 | 12 | 's3' => array( 13 | 'path' => '' 14 | ), 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /src/Console.php: -------------------------------------------------------------------------------- 1 | setTimeout(999999999); 11 | $process->run(); 12 | 13 | if ($process->isSuccessful()) 14 | { 15 | return true; 16 | } 17 | else 18 | { 19 | return $process->getErrorOutput(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/ConsoleTest.php: -------------------------------------------------------------------------------- 1 | console = new Console(); 13 | } 14 | 15 | public function testSuccess() 16 | { 17 | $this->assertTrue($this->console->run('true')); 18 | } 19 | 20 | public function testFailure() 21 | { 22 | $this->assertTrue($this->console->run('false') !== true); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/Databases/DatabaseInterface.php: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coreproc/laravel-db-backup", 3 | "description": "", 4 | "authors": [ 5 | { 6 | "name": "Chris Bautista", 7 | "email": "chrisbjr@gmail.com" 8 | }, 9 | { 10 | "name": "Johannes Schickling", 11 | "email": "schickling.j@gmail.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.4.0", 16 | "symfony/finder": "2.*", 17 | "illuminate/support": "4.2.*", 18 | "illuminate/database": "4.2.*", 19 | "aws/aws-sdk-php-laravel": "1.*" 20 | }, 21 | "require-dev": { 22 | "laravel/framework": "4.2.*", 23 | "orchestra/testbench": "2.2.*", 24 | "satooshi/php-coveralls": "0.6.*", 25 | "mockery/mockery": "dev-master" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "Coreproc\\LaravelDbBackup\\": "src/" 30 | } 31 | }, 32 | "minimum-stability": "stable" 33 | } 34 | -------------------------------------------------------------------------------- /src/Databases/SqliteDatabase.php: -------------------------------------------------------------------------------- 1 | console = $console; 14 | $this->databaseFile = $databaseFile; 15 | } 16 | 17 | public function dump($destinationFile) 18 | { 19 | $command = sprintf('cp %s %s', 20 | escapeshellarg($this->databaseFile), 21 | escapeshellarg($destinationFile) 22 | ); 23 | 24 | return $this->console->run($command); 25 | } 26 | 27 | public function restore($sourceFile) 28 | { 29 | $command = sprintf('cp -f %s %s', 30 | escapeshellarg($sourceFile), 31 | escapeshellarg($this->databaseFile) 32 | ); 33 | 34 | return $this->console->run($command); 35 | } 36 | 37 | public function getFileExtension() 38 | { 39 | return 'sqlite'; 40 | } 41 | } -------------------------------------------------------------------------------- /src/Commands/BaseCommand.php: -------------------------------------------------------------------------------- 1 | databaseBuilder = $databaseBuilder; 18 | $this->colors = new ConsoleColors(); 19 | } 20 | 21 | public function getDatabase($databaseDriver) 22 | { 23 | //$database = $database ?: Config::get('database.default'); 24 | $realConfig = Config::get('database.connections.' . $databaseDriver); 25 | 26 | return $this->databaseBuilder->getDatabase($realConfig); 27 | } 28 | 29 | protected function getDumpsPath() 30 | { 31 | return Config::get('laravel-db-backup::path'); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/Databases/PostgresDatabase.php: -------------------------------------------------------------------------------- 1 | console = $console; 17 | $this->database = $database; 18 | $this->user = $user; 19 | $this->password = $password; 20 | $this->host = $host; 21 | } 22 | 23 | public function dump($destinationFile) 24 | { 25 | $command = sprintf('PGPASSWORD=%s pg_dump -Fc --no-acl --no-owner -h %s -U %s %s > %s', 26 | escapeshellarg($this->password), 27 | escapeshellarg($this->host), 28 | escapeshellarg($this->user), 29 | escapeshellarg($this->database), 30 | escapeshellarg($destinationFile) 31 | ); 32 | 33 | return $this->console->run($command); 34 | } 35 | 36 | public function restore($sourceFile) 37 | { 38 | $command = sprintf('PGPASSWORD=%s pg_restore --verbose --clean --no-acl --no-owner -h %s -U %s -d %s %s', 39 | escapeshellarg($this->password), 40 | escapeshellarg($this->host), 41 | escapeshellarg($this->user), 42 | escapeshellarg($this->database), 43 | escapeshellarg($sourceFile) 44 | ); 45 | 46 | return $this->console->run($command); 47 | } 48 | 49 | public function getFileExtension() 50 | { 51 | return 'dump'; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/Commands/resources/NonEmptyFolder/hello.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.13 Distrib 5.1.51, for pc-linux-gnu (i686) 2 | -- 3 | -- Host: 127.0.0.1 Database: world 4 | -- ------------------------------------------------------ 5 | -- Server version 5.1.51-debug-log 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES latin1 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `City` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `City`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `City` ( 26 | `ID` int(11) NOT NULL AUTO_INCREMENT, 27 | `Name` char(35) NOT NULL DEFAULT '', 28 | `CountryCode` char(3) NOT NULL DEFAULT '', 29 | `District` char(20) NOT NULL DEFAULT '', 30 | `Population` int(11) NOT NULL DEFAULT '0', 31 | PRIMARY KEY (`ID`) 32 | ) ENGINE=MyISAM AUTO_INCREMENT=4080 DEFAULT CHARSET=latin1; 33 | /*!40101 SET character_set_client = @saved_cs_client */; 34 | -------------------------------------------------------------------------------- /tests/Commands/resources/NonEmptyFolder/world.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.13 Distrib 5.1.51, for pc-linux-gnu (i686) 2 | -- 3 | -- Host: 127.0.0.1 Database: world 4 | -- ------------------------------------------------------ 5 | -- Server version 5.1.51-debug-log 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES latin1 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `City` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `City`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `City` ( 26 | `ID` int(11) NOT NULL AUTO_INCREMENT, 27 | `Name` char(35) NOT NULL DEFAULT '', 28 | `CountryCode` char(3) NOT NULL DEFAULT '', 29 | `District` char(20) NOT NULL DEFAULT '', 30 | `Population` int(11) NOT NULL DEFAULT '0', 31 | PRIMARY KEY (`ID`) 32 | ) ENGINE=MyISAM AUTO_INCREMENT=4080 DEFAULT CHARSET=latin1; 33 | /*!40101 SET character_set_client = @saved_cs_client */; 34 | -------------------------------------------------------------------------------- /src/DatabaseBuilder.php: -------------------------------------------------------------------------------- 1 | console = new Console(); 11 | } 12 | 13 | public function getDatabase(array $realConfig) 14 | { 15 | 16 | switch ($realConfig['driver']) 17 | { 18 | case 'mysql': 19 | $this->buildMySQL($realConfig); 20 | break; 21 | 22 | case 'sqlite': 23 | $this->buildSqlite($realConfig); 24 | break; 25 | 26 | case 'pgsql': 27 | $this->buildPostgres($realConfig); 28 | break; 29 | 30 | default: 31 | throw new \Exception('Database driver not supported yet'); 32 | break; 33 | } 34 | 35 | return $this->database; 36 | } 37 | 38 | protected function buildMySQL(array $config) 39 | { 40 | $port = isset($config['port']) ? $config['port'] : 3306; 41 | $this->database = new Databases\MySQLDatabase( 42 | $this->console, 43 | $config['database'], 44 | $config['username'], 45 | $config['password'], 46 | $config['host'], 47 | $port 48 | ); 49 | } 50 | 51 | protected function buildSqlite(array $config) 52 | { 53 | $this->database = new Databases\SqliteDatabase( 54 | $this->console, 55 | $config['database'] 56 | ); 57 | } 58 | 59 | protected function buildPostgres(array $config) 60 | { 61 | $this->database = new Databases\PostgresDatabase( 62 | $this->console, 63 | $config['database'], 64 | $config['username'], 65 | $config['password'], 66 | $config['host'] 67 | ); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/Databases/MySQLDatabase.php: -------------------------------------------------------------------------------- 1 | console = $console; 19 | $this->database = $database; 20 | $this->user = $user; 21 | $this->password = $password; 22 | $this->host = $host; 23 | $this->port = $port; 24 | } 25 | 26 | public function dump($destinationFile) 27 | { 28 | $command = sprintf('mysqldump --user=%s --password=%s --host=%s --port=%s %s > %s', 29 | escapeshellarg($this->user), 30 | escapeshellarg($this->password), 31 | escapeshellarg($this->host), 32 | escapeshellarg($this->port), 33 | escapeshellarg($this->database), 34 | escapeshellarg($destinationFile) 35 | ); 36 | 37 | return $this->console->run($command); 38 | } 39 | 40 | public function restore($sourceFile) 41 | { 42 | $command = sprintf('mysql --user=%s --password=%s --host=%s --port=%s %s < %s', 43 | escapeshellarg($this->user), 44 | escapeshellarg($this->password), 45 | escapeshellarg($this->host), 46 | escapeshellarg($this->port), 47 | escapeshellarg($this->database), 48 | escapeshellarg($sourceFile) 49 | ); 50 | 51 | return $this->console->run($command); 52 | } 53 | 54 | public function getFileExtension() 55 | { 56 | return 'sql'; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/LaravelDbBackupServiceProvider.php: -------------------------------------------------------------------------------- 1 | package('coreproc/laravel-db-backup',null, __DIR__); 23 | 24 | $this->app->register('Aws\Laravel\AwsServiceProvider'); 25 | 26 | $loader = \Illuminate\Foundation\AliasLoader::getInstance(); 27 | $loader->alias('AWS', 'Aws\Laravel\AwsFacade'); 28 | } 29 | 30 | /** 31 | * Register the service provider. 32 | * 33 | * @return void 34 | */ 35 | public function register() 36 | { 37 | $databaseBuilder = new DatabaseBuilder(); 38 | 39 | $this->app['db.backup'] = $this->app->share(function ($app) use ($databaseBuilder) { 40 | return new Commands\BackupCommand($databaseBuilder); 41 | }); 42 | 43 | $this->app['db.restore'] = $this->app->share(function ($app) use ($databaseBuilder) { 44 | return new Commands\RestoreCommand($databaseBuilder); 45 | }); 46 | 47 | $this->commands( 48 | 'db.backup', 49 | 'db.restore' 50 | ); 51 | } 52 | 53 | /** 54 | * Get the services provided by the provider. 55 | * 56 | * @return array 57 | */ 58 | public function provides() 59 | { 60 | return array(); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | 6 | # Created by https://www.gitignore.io 7 | 8 | ### OSX ### 9 | .DS_Store 10 | .AppleDouble 11 | .LSOverride 12 | 13 | # Icon must end with two \r 14 | Icon 15 | 16 | 17 | # Thumbnails 18 | ._* 19 | 20 | # Files that might appear on external disk 21 | .Spotlight-V100 22 | .Trashes 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | 32 | ### Windows ### 33 | # Windows image file caches 34 | Thumbs.db 35 | ehthumbs.db 36 | 37 | # Folder config file 38 | Desktop.ini 39 | 40 | # Recycle Bin used on file shares 41 | $RECYCLE.BIN/ 42 | 43 | # Windows Installer files 44 | *.cab 45 | *.msi 46 | *.msm 47 | *.msp 48 | 49 | # Windows shortcuts 50 | *.lnk 51 | 52 | 53 | ### Intellij ### 54 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 55 | 56 | /*.iml 57 | 58 | ## Directory-based project format: 59 | .idea/ 60 | # if you remove the above rule, at least ignore the follwing: 61 | 62 | # User-specific stuff: 63 | # .idea/workspace.xml 64 | # .idea/tasks.xml 65 | # .idea/dictionaries 66 | 67 | # Sensitive or high-churn files: 68 | # .idea/dataSources.ids 69 | # .idea/dataSources.xml 70 | # .idea/sqlDataSources.xml 71 | # .idea/dynamic.xml 72 | # .idea/uiDesigner.xml 73 | 74 | # Gradle: 75 | # .idea/gradle.xml 76 | # .idea/libraries 77 | 78 | # Mongo Explorer plugin: 79 | # .idea/mongoSettings.xml 80 | 81 | ## File-based project format: 82 | *.ipr 83 | *.iws 84 | 85 | ## Plugin-specific files: 86 | 87 | # IntelliJ 88 | out/ 89 | 90 | # mpeltonen/sbt-idea plugin 91 | .idea_modules/ 92 | 93 | # JIRA plugin 94 | atlassian-ide-plugin.xml 95 | 96 | # Crashlytics plugin (for Android Studio and IntelliJ) 97 | com_crashlytics_export_strings.xml 98 | 99 | -------------------------------------------------------------------------------- /tests/Databases/SqliteDatabaseTest.php: -------------------------------------------------------------------------------- 1 | console = m::mock('Coreproc\LaravelDbBackup\Console'); 15 | $this->database = new SqliteDatabase($this->console, 'testDatabase.sqlite'); 16 | } 17 | 18 | public function tearDown() 19 | { 20 | m::close(); 21 | } 22 | 23 | public function testDump() 24 | { 25 | $this->console->shouldReceive('run') 26 | ->with("cp 'testDatabase.sqlite' 'testfile.sqlite'") 27 | ->once() 28 | ->andReturn(true); 29 | 30 | $this->assertTrue($this->database->dump('testfile.sqlite')); 31 | } 32 | 33 | public function testDumpFails() 34 | { 35 | $this->console->shouldReceive('run') 36 | ->with("cp 'testDatabase.sqlite' 'testfile.sqlite'") 37 | ->once() 38 | ->andReturn(false); 39 | 40 | $this->assertFalse($this->database->dump('testfile.sqlite')); 41 | } 42 | 43 | public function testRestore() 44 | { 45 | $this->console->shouldReceive('run') 46 | ->with("cp -f 'testfile.sqlite' 'testDatabase.sqlite'") 47 | ->once() 48 | ->andReturn(true); 49 | 50 | $this->assertTrue($this->database->restore('testfile.sqlite')); 51 | } 52 | 53 | public function testRestoreFails() 54 | { 55 | $this->console->shouldReceive('run') 56 | ->with("cp -f 'testfile.sqlite' 'testDatabase.sqlite'") 57 | ->once() 58 | ->andReturn(false); 59 | 60 | $this->assertFalse($this->database->restore('testfile.sqlite')); 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /tests/DatabaseBuilderTest.php: -------------------------------------------------------------------------------- 1 | 'mysql', 12 | 'host' => 'localhost', 13 | 'database' => 'database', 14 | 'username' => 'root', 15 | 'password' => '', 16 | 'port' => '3307', 17 | ); 18 | 19 | $databaseBuilder = new DatabaseBuilder(); 20 | $database = $databaseBuilder->getDatabase($config); 21 | 22 | $this->assertInstanceOf('Coreproc\LaravelDbBackup\Databases\MySQLDatabase', $database); 23 | } 24 | 25 | public function testSqlite() 26 | { 27 | $config = array( 28 | 'driver' => 'sqlite', 29 | 'database' => __DIR__.'/../database/production.sqlite', 30 | ); 31 | 32 | $databaseBuilder = new DatabaseBuilder(); 33 | $database = $databaseBuilder->getDatabase($config); 34 | 35 | $this->assertInstanceOf('Coreproc\LaravelDbBackup\Databases\SqliteDatabase', $database); 36 | } 37 | 38 | public function testPostgres() { 39 | $config = array( 40 | 'driver' => 'pgsql', 41 | 'host' => 'localhost', 42 | 'database' => 'database', 43 | 'username' => 'root', 44 | 'password' => 'paso', 45 | ); 46 | 47 | $databaseBuilder = new DatabaseBuilder(); 48 | $database = $databaseBuilder->getDatabase($config); 49 | 50 | $this->assertInstanceOf('Coreproc\LaravelDbBackup\Databases\PostgresDatabase', $database); 51 | } 52 | 53 | public function testUnsupported() 54 | { 55 | $config = array( 56 | 'driver' => 'unsupported', 57 | ); 58 | 59 | $this->setExpectedException('Exception'); 60 | 61 | $databaseBuilder = new DatabaseBuilder(); 62 | $database = $databaseBuilder->getDatabase($config); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /tests/Databases/MySQLDatabaseTest.php: -------------------------------------------------------------------------------- 1 | console = m::mock('Coreproc\LaravelDbBackup\Console'); 15 | $this->database = new MySQLDatabase($this->console, 'testDatabase', 'testUser', 'password', 'localhost', '3306'); 16 | } 17 | 18 | public function tearDown() 19 | { 20 | m::close(); 21 | } 22 | 23 | public function testDump() 24 | { 25 | $this->console->shouldReceive('run') 26 | ->with("mysqldump --user='testUser' --password='password' --host='localhost' --port='3306' 'testDatabase' > 'testfile.sql'") 27 | ->once() 28 | ->andReturn(true); 29 | 30 | $this->assertTrue($this->database->dump('testfile.sql')); 31 | } 32 | 33 | public function testDumpFails() 34 | { 35 | $this->console->shouldReceive('run') 36 | ->with("mysqldump --user='testUser' --password='password' --host='localhost' --port='3306' 'testDatabase' > 'testfile.sql'") 37 | ->once() 38 | ->andReturn(false); 39 | 40 | $this->assertFalse($this->database->dump('testfile.sql')); 41 | } 42 | 43 | public function testRestore() 44 | { 45 | $this->console->shouldReceive('run') 46 | ->with("mysql --user='testUser' --password='password' --host='localhost' --port='3306' 'testDatabase' < 'testfile.sql'") 47 | ->once() 48 | ->andReturn(true); 49 | 50 | $this->assertTrue($this->database->restore('testfile.sql')); 51 | } 52 | 53 | public function testRestoreFails() 54 | { 55 | $this->console->shouldReceive('run') 56 | ->with("mysql --user='testUser' --password='password' --host='localhost' --port='3306' 'testDatabase' < 'testfile.sql'") 57 | ->once() 58 | ->andReturn(false); 59 | 60 | $this->assertFalse($this->database->restore('testfile.sql')); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /tests/Databases/PostgresDatabaseTest.php: -------------------------------------------------------------------------------- 1 | console = m::mock('Coreproc\LaravelDbBackup\Console'); 15 | $this->database = new PostgresDatabase($this->console, 'testDatabase', 'testUser', 'password', 'localhost'); 16 | } 17 | 18 | public function tearDown() 19 | { 20 | m::close(); 21 | } 22 | 23 | public function testDump() 24 | { 25 | $this->console->shouldReceive('run') 26 | ->with("PGPASSWORD='password' pg_dump -Fc --no-acl --no-owner -h 'localhost' -U 'testUser' 'testDatabase' > 'testfile.dump'") 27 | ->once() 28 | ->andReturn(true); 29 | 30 | $this->assertTrue($this->database->dump('testfile.dump')); 31 | } 32 | 33 | public function testDumpFails() 34 | { 35 | $this->console->shouldReceive('run') 36 | ->with("PGPASSWORD='password' pg_dump -Fc --no-acl --no-owner -h 'localhost' -U 'testUser' 'testDatabase' > 'testfile.dump'") 37 | ->once() 38 | ->andReturn(false); 39 | 40 | $this->assertFalse($this->database->dump('testfile.dump')); 41 | } 42 | 43 | public function testRestore() 44 | { 45 | $this->console->shouldReceive('run') 46 | ->with("PGPASSWORD='password' pg_restore --verbose --clean --no-acl --no-owner -h 'localhost' -U 'testUser' -d 'testDatabase' 'testfile.dump'") 47 | ->once() 48 | ->andReturn(true); 49 | 50 | $this->assertTrue($this->database->restore('testfile.dump')); 51 | } 52 | 53 | public function testRestoreFails() 54 | { 55 | $this->console->shouldReceive('run') 56 | ->with("PGPASSWORD='password' pg_restore --verbose --clean --no-acl --no-owner -h 'localhost' -U 'testUser' -d 'testDatabase' 'testfile.dump'") 57 | ->once() 58 | ->andReturn(false); 59 | 60 | $this->assertFalse($this->database->restore('testfile.dump')); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/Commands/RestoreCommand.php: -------------------------------------------------------------------------------- 1 | database = $this->getDatabase(Config::get('database.default')); 20 | 21 | $databaseOption = $this->input->getOption('database'); 22 | 23 | if ( ! empty($databaseOption)) { 24 | $this->database = $this->getDatabase($databaseOption); 25 | } 26 | 27 | $fileName = $this->argument('dump'); 28 | 29 | if ($fileName) 30 | { 31 | $this->restoreDump($fileName); 32 | } 33 | else 34 | { 35 | $this->listAllDumps(); 36 | } 37 | } 38 | 39 | protected function restoreDump($fileName) 40 | { 41 | $sourceFile = $this->getDumpsPath() . $fileName; 42 | 43 | $status = $this->database->restore($sourceFile); 44 | 45 | if ($status === true) 46 | { 47 | $this->line(sprintf($this->colors->getColoredString("\n".'%s was successfully restored.'."\n",'green'), $fileName)); 48 | } 49 | else 50 | { 51 | $this->line($this->colors->getColoredString("\n".'Database restore failed.'."\n",'red')); 52 | } 53 | } 54 | 55 | protected function listAllDumps() 56 | { 57 | $finder = new Finder(); 58 | $finder->files()->in($this->getDumpsPath()); 59 | 60 | if ($finder->count() > 0) 61 | { 62 | $this->line($this->colors->getColoredString("\n".'Please select one of the following dumps:'."\n",'white')); 63 | 64 | $finder->sortByName(); 65 | $count = count($finder); 66 | $i=0; 67 | foreach ($finder as $dump) 68 | { 69 | $i++; 70 | if($i!=$count){ 71 | $this->line($this->colors->getColoredString($dump->getFilename(),'brown')); 72 | }else{ 73 | $this->line($this->colors->getColoredString($dump->getFilename()."\n",'brown')); 74 | } 75 | } 76 | } 77 | else 78 | { 79 | $this->line($this->colors->getColoredString("\n".'You haven\'t saved any dumps.'."\n",'brown')); 80 | } 81 | } 82 | 83 | protected function getArguments() 84 | { 85 | return array( 86 | array('dump', InputArgument::OPTIONAL, 'Filename of the dump') 87 | ); 88 | } 89 | 90 | protected function getOptions() 91 | { 92 | return array( 93 | array('database', null, InputOption::VALUE_OPTIONAL, 'The database connection to restore to'), 94 | ); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/ConsoleColors.php: -------------------------------------------------------------------------------- 1 | foreground_colors['black'] = '0;30'; 12 | $this->foreground_colors['dark_gray'] = '1;30'; 13 | $this->foreground_colors['blue'] = '0;34'; 14 | $this->foreground_colors['light_blue'] = '1;34'; 15 | $this->foreground_colors['green'] = '0;32'; 16 | $this->foreground_colors['light_green'] = '1;32'; 17 | $this->foreground_colors['cyan'] = '0;36'; 18 | $this->foreground_colors['light_cyan'] = '1;36'; 19 | $this->foreground_colors['red'] = '0;31'; 20 | $this->foreground_colors['light_red'] = '1;31'; 21 | $this->foreground_colors['purple'] = '0;35'; 22 | $this->foreground_colors['light_purple'] = '1;35'; 23 | $this->foreground_colors['brown'] = '0;33'; 24 | $this->foreground_colors['yellow'] = '1;33'; 25 | $this->foreground_colors['light_gray'] = '0;37'; 26 | $this->foreground_colors['white'] = '1;37'; 27 | 28 | $this->background_colors['black'] = '40'; 29 | $this->background_colors['red'] = '41'; 30 | $this->background_colors['green'] = '42'; 31 | $this->background_colors['yellow'] = '43'; 32 | $this->background_colors['blue'] = '44'; 33 | $this->background_colors['magenta'] = '45'; 34 | $this->background_colors['cyan'] = '46'; 35 | $this->background_colors['light_gray'] = '47'; 36 | } 37 | 38 | // Returns colored string 39 | public function getColoredString($string, $foreground_color = null, $background_color = null) 40 | { 41 | $colored_string = ""; 42 | 43 | // Check if given foreground color found 44 | if (isset($this->foreground_colors[$foreground_color])) { 45 | $colored_string .= "\033[" . $this->foreground_colors[$foreground_color] . "m"; 46 | } 47 | // Check if given background color found 48 | if (isset($this->background_colors[$background_color])) { 49 | $colored_string .= "\033[" . $this->background_colors[$background_color] . "m"; 50 | } 51 | 52 | // Add string and end coloring 53 | $colored_string .= $string . "\033[0m"; 54 | 55 | return $colored_string; 56 | } 57 | 58 | // Returns all foreground color names 59 | public function getForegroundColors() 60 | { 61 | return array_keys($this->foreground_colors); 62 | } 63 | 64 | // Returns all background color names 65 | public function getBackgroundColors() 66 | { 67 | return array_keys($this->background_colors); 68 | } 69 | } 70 | 71 | ?> -------------------------------------------------------------------------------- /tests/Commands/RestoreCommandTest.php: -------------------------------------------------------------------------------- 1 | databaseMock = m::mock('Coreproc\LaravelDbBackup\Databases\DatabaseInterface'); 18 | $this->databaseBuilderMock = m::mock('Coreproc\LaravelDbBackup\DatabaseBuilder'); 19 | $this->databaseBuilderMock->shouldReceive('getDatabase') 20 | ->once() 21 | ->andReturn($this->databaseMock); 22 | 23 | $command = new RestoreCommand($this->databaseBuilderMock); 24 | 25 | $this->tester = new CommandTester($command); 26 | } 27 | 28 | public function tearDown() 29 | { 30 | m::close(); 31 | } 32 | 33 | protected function getPackageProviders() 34 | { 35 | return array( 36 | 'Coreproc\LaravelDbBackup\LaravelDbBackupServiceProvider', 37 | ); 38 | } 39 | 40 | public function testSuccessfulRestore() 41 | { 42 | $testDumpFile = storage_path() . '/dumps/testDump.sql'; 43 | 44 | $this->databaseMock->shouldReceive('restore') 45 | ->with($testDumpFile) 46 | ->once() 47 | ->andReturn(true); 48 | 49 | $this->tester->execute(array( 50 | 'dump' => 'testDump.sql' 51 | )); 52 | 53 | $this->assertRegExp("/^(\\033\[[0-9;]*m)*(\\n)*testDump.sql was successfully restored.(\\n)*(\\033\[0m)*$/", $this->tester->getDisplay()); 54 | } 55 | 56 | public function testFailingRestore() 57 | { 58 | $testDumpFile = storage_path() . '/dumps/testDump.sql'; 59 | 60 | $this->databaseMock->shouldReceive('restore') 61 | ->with($testDumpFile) 62 | ->once() 63 | ->andReturn(false); 64 | 65 | $this->tester->execute(array( 66 | 'dump' => 'testDump.sql' 67 | )); 68 | 69 | $this->assertRegExp("/^(\\033\[[0-9;]*m)*(\\n)*Database restore failed.(\\n)*(\\033\[0m)*$/", $this->tester->getDisplay()); 70 | } 71 | 72 | public function testDumpListForEmptyFolder() 73 | { 74 | $this->app->config->set('database.backup.path', __DIR__ . '/resources/EmptyFolder'); 75 | 76 | $this->tester->execute(array()); 77 | 78 | $this->assertRegExp("/^(\\033\[[0-9;]*m)*(\\n)*You haven't saved any dumps.(\\n)*(\\033\[0m)*$/", $this->tester->getDisplay()); 79 | } 80 | 81 | public function testDumpListForNonEmptyFolder() 82 | { 83 | $this->app->config->set('database.backup.path', __DIR__ . '/resources/NonEmptyFolder'); 84 | 85 | $this->tester->execute(array()); 86 | //@TODO: Need to find the good regex 87 | //$this->assertRegExp("/^(\\033\[[0-9;]*m)*(\\n)*Please select one of the following dumps:(\\n)*(\\033\[0m)*(\\033\[[0-9;]*m)*(\\n)*hello.sql(\\n)*(\\033\[0m)*(\\033\[[0-9;]*m)*(\\n)*world.sql(\\n)*(\\033\[0m)*$/", $this->tester->getDisplay()); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/Commands/BackupCommandTest.php: -------------------------------------------------------------------------------- 1 | databaseMock = m::mock('Coreproc\LaravelDbBackup\Databases\DatabaseInterface'); 18 | $this->databaseBuilderMock = m::mock('Coreproc\LaravelDbBackup\DatabaseBuilder'); 19 | $this->databaseBuilderMock->shouldReceive('getDatabase') 20 | ->once() 21 | ->andReturn($this->databaseMock); 22 | 23 | $command = new BackupCommand($this->databaseBuilderMock); 24 | 25 | $this->tester = new CommandTester($command); 26 | } 27 | 28 | public function tearDown() 29 | { 30 | m::close(); 31 | } 32 | 33 | protected function getPackageProviders() 34 | { 35 | return array( 36 | 'Coreproc\LaravelDbBackup\LaravelDbBackupServiceProvider', 37 | 'Aws\Laravel\AwsServiceProvider', 38 | ); 39 | } 40 | 41 | protected function getPackageAliases() 42 | { 43 | return array( 44 | 'AWS' => 'Aws\Laravel\AwsFacade', 45 | ); 46 | } 47 | 48 | public function testSuccessfulBackup() 49 | { 50 | 51 | $this->databaseMock->shouldReceive('getFileExtension') 52 | ->once() 53 | ->andReturn('sql'); 54 | 55 | $this->databaseMock->shouldReceive('dump') 56 | ->once() 57 | ->andReturn(true); 58 | 59 | $this->tester->execute(array()); 60 | 61 | $this->assertRegExp("/^(\\033\[[0-9;]*m)*(\\n)*Database backup was successful. [A-Za-z0-9_]{10,}.sql was saved in the dumps folder.(\\n)*(\\033\[0m)*$/", $this->tester->getDisplay()); 62 | } 63 | 64 | public function testFailingBackup() 65 | { 66 | 67 | $this->databaseMock->shouldReceive('getFileExtension') 68 | ->once() 69 | ->andReturn('sql'); 70 | 71 | $this->databaseMock->shouldReceive('dump') 72 | ->once() 73 | ->andReturn('Error message'); 74 | 75 | $this->tester->execute(array()); 76 | 77 | $this->assertRegExp("/^(\\033\[[0-9;]*m)*(\\n)*Database backup failed. Error message(\\n)*(\\033\[0m)*$/", $this->tester->getDisplay()); 78 | } 79 | 80 | public function testUploadS3() 81 | { 82 | $s3Mock = m::mock(); 83 | $s3Mock->shouldReceive('putObject') 84 | ->andReturn(true); 85 | 86 | AWS::shouldReceive('get') 87 | ->once() 88 | ->with('s3') 89 | ->andReturn($s3Mock); 90 | 91 | $this->databaseMock->shouldReceive('getFileExtension') 92 | ->once() 93 | ->andReturn('sql'); 94 | 95 | $this->databaseMock->shouldReceive('dump') 96 | ->once() 97 | ->andReturn(true); 98 | 99 | $this->tester->execute(array( 100 | '--upload-s3' => 'bucket-title' 101 | )); 102 | 103 | // $this->assertRegExp("/^(\\033\[[0-9;]*m)*(\\n)*Database backup was successful. [A-Za-z0-9_]{10,}.sql was saved in the dumps folder.(\\n)*(\\033\[0m)*(\\033\[[0-9;]*m)*(\\n)*Upload complete.(\\n)*(\\033\[0m)*$/", $this->tester->getDisplay()); 104 | 105 | } 106 | 107 | public function testAbsolutePathAsFilename() 108 | { 109 | 110 | $this->databaseMock->shouldReceive('getFileExtension') 111 | ->never(); 112 | 113 | $this->databaseMock->shouldReceive('dump') 114 | ->once() 115 | ->andReturn(true); 116 | 117 | $filename = '/home/dummy/mydump.sql'; 118 | 119 | $this->tester->execute(array( 120 | 'filename' => $filename 121 | )); 122 | $regex = "/^(\\033\[[0-9;]*m)*(\\n)*Database backup was successful. Saved to \/home\/dummy\/mydump.sql(\\n)*(\\033\[0m)*$/"; 123 | $this->assertRegExp($regex, $this->tester->getDisplay()); 124 | 125 | } 126 | 127 | public function testRelativePathAsFilename() 128 | { 129 | 130 | $this->databaseMock->shouldReceive('getFileExtension') 131 | ->never(); 132 | 133 | $this->databaseMock->shouldReceive('dump') 134 | ->once() 135 | ->andReturn(true); 136 | 137 | $filename = 'dummy/mydump.sql'; 138 | 139 | $this->tester->execute(array( 140 | 'filename' => $filename 141 | )); 142 | $path = str_replace('/', '\/', getcwd()); 143 | $regex = "/^(\\033\[[0-9;]*m)*(\\n)*Database backup was successful. Saved to " . $path . "\/dummy\/mydump.sql(\\n)*(\\033\[0m)*$/"; 144 | $this->assertRegExp($regex, $this->tester->getDisplay()); 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Laravel DB Backup 2 | ================= 3 | 4 | [![Join the chat at https://gitter.im/CoreProc/laravel-db-backup](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/CoreProc/laravel-db-backup?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | 6 | Artisan command to backup your database. Built for Laravel 4.2. Originally forked from [schickling/laravel-backup](https://github.com/schickling/laravel-backup) but modified to push in more features like data retention from Amazon S3 and Slack notifications. 7 | 8 | ## Quick start 9 | 10 | ### Required setup 11 | 12 | In the `require` key of `composer.json` file add the following 13 | 14 | "coreproc/laravel-db-backup": "0.*" 15 | 16 | Run the Composer update comand 17 | 18 | $ composer update 19 | 20 | In your `app/config/app.php` add `'Coreproc\LaravelDbBackup\LaravelDbBackupServiceProvider'` to the end of the `$providers` array 21 | 22 | 'providers' => array( 23 | 24 | 'Illuminate\Foundation\Providers\ArtisanServiceProvider', 25 | 'Illuminate\Auth\AuthServiceProvider', 26 | ... 27 | 'EllipseSynergie\ApiResponse\Laravel\ResponseServiceProvider', 28 | 'Coreproc\LaravelDbBackup\LaravelDbBackupServiceProvider', 29 | 30 | ), 31 | 32 | ## Usage 33 | 34 | ### Basic Usage 35 | 36 | You can quickly backup your database using the command line below 37 | 38 | `php artisan db:backup` 39 | 40 | This command will backup the database that your Laravel application is connected to. This means that the `default` configuration from `app/config/database.php` will be used. 41 | 42 | By default, the file will be saved in the `app/storage/dumps` path and will be named like this: `{database_name}_{unix_timestamp}.sql`. 43 | 44 | If you want to use another database, just add another configuration to the `connection` value. A sample configuration in `app/config/database.php` would look like this: 45 | 46 | 'connections' => array( 47 | 48 | 'dbconnection1' => array( 49 | 'driver' => 'mysql', 50 | 'host' => 'localhost', 51 | 'database' => 'db1', 52 | 'username' => 'user', 53 | 'password' => 'password', 54 | 'charset' => 'utf8', 55 | 'collation' => 'utf8_unicode_ci', 56 | 'prefix' => '', 57 | 'slackWebhookPath' => 'T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' 58 | ), 59 | 60 | 'dbconnection2' => array( 61 | 'driver' => 'mysql', 62 | 'host' => 'localhost', 63 | 'database' => 'db1', 64 | 'username' => 'user', 65 | 'password' => 'password', 66 | 'charset' => 'utf8', 67 | 'collation' => 'utf8_unicode_ci', 68 | 'prefix' => '', 69 | 'slackWebhookPath' => 'T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' 70 | ), 71 | 72 | ), 73 | 74 | So if you want to back up, let's say, dbconnection2, you would add the `--database` option to our artisan command. 75 | 76 | `php artisan db:backup --database=dbconnection2` 77 | 78 | ### Slack Integration 79 | 80 | [Slack](https://slack.com) is a platform/service for team communication. Using the [Incoming Webhooks](https://api.slack.com/incoming-webhooks) integration you can send real-time messages to any channel on your team's Slack. 81 | 82 | If you'll notice, we have added an extra variable to the database configuration (`slackWebhookPath`). To begin receiving your database backup notifications you first have to add the [Incoming Webhooks integration](https://my.slack.com/services/new/incoming-webhook/) for your Slack team. Once set up you will be given a unique `Webhook URL` which will look something like this: *`https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX`*. Just copy your unique webhook path (`T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX`) and add it to your database configuration. 83 | 84 | To disable the Slack integrations, either leave the value blank or you can disable Slack notifications manually by using the `--disable-slack` option. Here is an example: 85 | 86 | ``php artisan db:backup --database=dbconnection2 --disable-slack` 87 | 88 | ### Upload to Amazon S3 89 | 90 | To upload to you Amazon S3 bucket, you will need to put in your AWS settings first. By default, this package is shipped with the [aws/aws-sdk-php-laravel](https://github.com/aws/aws-sdk-php-laravel) package so we'll modify the settings from there. 91 | 92 | To bring out the config file of the AWS library, you'll need to generate it by using the command below: 93 | 94 | `php artisan config:publish aws/aws-sdk-php-laravel` 95 | 96 | You will find the AWS config file in the following path: `app/config/packages/aws/aws-sdk-php-laravel/config.php`. Fill in the appropriate values. 97 | 98 | To upload the database backup to S3, use the `--upload-s3` option: 99 | 100 | `php artisan db:backup --database=dbconnection2 --upload-s3=s3_bucket_name` 101 | 102 | Change the value of the `--upload-s3` to the name of the bucket you want to upload to. 103 | 104 | If you want to keep only S3 copy of your backups, set `--s3-only`, which will delete local copy of backup when S3 upload is enabled. 105 | 106 | `php artisan db:backup --database=dbconnection2 --upload-s3=s3_bucket_name --s3-only=true` 107 | 108 | ### Database Retention (S3 only for now) 109 | 110 | If you want to only keep a certain number of copies of your database, you can set the `--data-retention-s3` option to the number of days you want to retain your data. Here is an example: 111 | 112 | `php artisan db:backup --database=dbconnection2 --data-retention=30` 113 | 114 | ### Change filename 115 | 116 | You can change the name of the backup file with the `--filename` option. All filenames will be appended with the unix timestamp. 117 | 118 | `php artisan db:backup --database=dbconnection2 --filename=test` 119 | 120 | ### Save as ZIP archive 121 | 122 | To create zip archive instead of raw .sql file, set `--archive` value. 123 | 124 | `php artisan db:backup --database=dbconnection2 --data-retention=30 --archive=true` -------------------------------------------------------------------------------- /src/Commands/BackupCommand.php: -------------------------------------------------------------------------------- 1 | option('database'); 22 | 23 | if ( ! empty($databaseOption)) { 24 | $databaseDriver = $databaseOption; 25 | } 26 | 27 | $database = $this->getDatabase($databaseDriver); 28 | $dbConnectionConfig = Config::get('database.connections.' . $databaseDriver); 29 | 30 | $this->checkDumpFolder(); 31 | 32 | $customFilename = $this->argument('filename'); 33 | 34 | if ($customFilename) { 35 | // Is it an absolute path? 36 | if (substr($customFilename, 0, 1) == '/') { 37 | $this->filePath = $customFilename; 38 | $this->fileName = basename($this->filePath); 39 | } // It's relative path? 40 | else { 41 | $this->filePath = getcwd() . '/' . $customFilename; 42 | $this->fileName = basename($this->filePath) . '_' . time(); 43 | } 44 | } else { 45 | $this->fileName = $dbConnectionConfig['database'] . '_' . time() . '.' . $database->getFileExtension(); 46 | $this->filePath = rtrim($this->getDumpsPath(), '/') . '/' . $this->fileName; 47 | } 48 | 49 | $status = $database->dump($this->filePath); 50 | 51 | if ($status === true) { 52 | 53 | // create zip archive 54 | if ($this->option('archive')) { 55 | $zip = new \ZipArchive(); 56 | $zipFileName = $dbConnectionConfig['database'] . '_' . time() . '.zip'; 57 | $zipFilePath = dirname($this->filePath) . '/' . $zipFileName; 58 | 59 | if ($zip->open($zipFilePath, \ZipArchive::CREATE) === true) { 60 | $zip->addFile($this->filePath); 61 | $zip->close(); 62 | 63 | // delete .sql files 64 | unlink($this->filePath); 65 | 66 | // change filename and filepath to zip 67 | $this->filePath = $zipFilePath; 68 | $this->fileName = $zipFileName; 69 | } 70 | } 71 | 72 | // display success message 73 | if ($customFilename) { 74 | $this->line(sprintf($this->colors->getColoredString("\n" . 'Database backup was successful. Saved to %s' . "\n", 'green'), $this->filePath)); 75 | } else { 76 | $this->line(sprintf($this->colors->getColoredString("\n" . 'Database backup was successful. %s was saved in the dumps folder.' . "\n", 'green'), $this->fileName)); 77 | } 78 | 79 | // upload to s3 80 | if ($this->option('upload-s3')) { 81 | $this->uploadS3(); 82 | $this->line($this->colors->getColoredString("\n" . 'Upload complete.' . "\n", 'green')); 83 | if ($this->option('data-retention-s3')) { 84 | $this->dataRetentionS3(); 85 | } 86 | 87 | // remove local archive if desired 88 | if ($this->option('s3-only')) { 89 | unlink($this->filePath); 90 | } 91 | } 92 | 93 | if ( ! empty($dbConnectionConfig['slackWebhookPath'])) { 94 | $disableSlackOption = $this->option('disable-slack'); 95 | if ( ! $disableSlackOption) $this->notifySlack($dbConnectionConfig); 96 | } 97 | 98 | } else { 99 | // todo 100 | $this->line(sprintf($this->colors->getColoredString("\n" . 'Database backup failed. %s' . "\n", 'red'), $status)); 101 | } 102 | } 103 | 104 | /** 105 | * Get the console command arguments. 106 | * 107 | * @return array 108 | */ 109 | protected function getArguments() 110 | { 111 | return array( 112 | array('filename', InputArgument::OPTIONAL, 'Filename or -path for the dump.'), 113 | ); 114 | } 115 | 116 | protected function getOptions() 117 | { 118 | return array( 119 | array('database', null, InputOption::VALUE_REQUIRED, 'The database connection to backup'), 120 | array('upload-s3', 'u', InputOption::VALUE_OPTIONAL, 'Upload the dump to your S3 bucket'), 121 | array('path-s3', null, InputOption::VALUE_OPTIONAL, 'The folder in which to save the backup'), 122 | array('data-retention-s3', null, InputOption::VALUE_OPTIONAL, 'Number of days to retain backups'), 123 | array('disable-slack', null, InputOption::VALUE_NONE, 'Number of days to retain backups'), 124 | array('archive', null, InputOption::VALUE_OPTIONAL, 'Create zip archive'), 125 | array('s3-only', null, InputOption::VALUE_OPTIONAL, 'Delete local archive after S3 upload'), 126 | ); 127 | } 128 | 129 | protected function checkDumpFolder() 130 | { 131 | $dumpsPath = $this->getDumpsPath(); 132 | 133 | if ( ! is_dir($dumpsPath)) { 134 | mkdir($dumpsPath); 135 | } 136 | } 137 | 138 | protected function uploadS3() 139 | { 140 | $bucket = $this->option('upload-s3'); 141 | $s3 = AWS::get('s3'); 142 | 143 | $s3->putObject(array( 144 | 'Bucket' => $bucket, 145 | 'Key' => $this->getS3DumpsPath() . '/' . $this->fileName, 146 | 'SourceFile' => $this->filePath, 147 | )); 148 | } 149 | 150 | protected function getS3DumpsPath() 151 | { 152 | if ($this->option('path-s3')) { 153 | $path = $this->option('path-s3'); 154 | } else { 155 | $path = Config::get('laravel-db-backup::s3.path', 'databases'); 156 | } 157 | 158 | return $path; 159 | } 160 | 161 | private function dataRetentionS3() 162 | { 163 | if ( ! $this->option('data-retention-s3')) { 164 | return; 165 | } 166 | 167 | $dataRetention = (int) $this->option('data-retention-s3'); 168 | 169 | if ($dataRetention <= 0) { 170 | $this->error("Data retention should be a number"); 171 | return; 172 | } 173 | 174 | $bucket = $this->option('upload-s3'); 175 | $s3 = AWS::get('s3'); 176 | 177 | $list = $s3->listObjects(array( 178 | 'Bucket' => $bucket, 179 | 'Marker' => $this->getS3DumpsPath(), 180 | )); 181 | 182 | $timestampForRetention = strtotime('-' . $dataRetention . ' days'); 183 | $this->info('Retaining data where date is greater than ' . date('Y-m-d', $timestampForRetention)); 184 | 185 | $contents = $list['Contents']; 186 | 187 | $deleteCount = 0; 188 | foreach ($contents as $fileArray) { 189 | $filePathArray = explode('/', $fileArray['Key']); 190 | $filename = $filePathArray[count($filePathArray) - 1]; 191 | 192 | $filenameExplode = explode('_', $filename); 193 | 194 | $fileTimestamp = explode('.', $filenameExplode[count($filenameExplode) - 1])[0]; 195 | 196 | if ($timestampForRetention > $fileTimestamp) { 197 | $this->info("The following file is beyond data retention and was deleted: {$fileArray['Key']}"); 198 | // delete 199 | $s3->deleteObject(array( 200 | 'Bucket' => $bucket, 201 | 'Key' => $fileArray['Key'] 202 | )); 203 | $deleteCount++; 204 | } 205 | } 206 | 207 | if ($deleteCount > 0) { 208 | $this->info($deleteCount . ' file(s) were deleted.'); 209 | } 210 | 211 | $this->info(""); 212 | } 213 | 214 | private function notifySlack($databaseConfig) 215 | { 216 | $this->info('Sending slack notification..'); 217 | $data['text'] = "A backup of the {$databaseConfig['database']} database at {$databaseConfig['host']} has been created."; 218 | $data['username'] = "Database Backup"; 219 | $data['icon_url'] = "https://s3-ap-northeast-1.amazonaws.com/coreproc/images/icon_database.png"; 220 | 221 | $content = json_encode($data); 222 | 223 | $command = "curl -X POST --data-urlencode 'payload={$content}' https://hooks.slack.com/services/{$databaseConfig['slackWebhookPath']}"; 224 | 225 | shell_exec($command); 226 | $this->info('Slack notification sent!'); 227 | } 228 | 229 | } 230 | --------------------------------------------------------------------------------