From c256fd51e2786873caa2db8bc74de826f8314555 Mon Sep 17 00:00:00 2001 From: Daniel Love Date: Mon, 9 May 2022 10:31:12 -0600 Subject: [PATCH] Draft. Updating Drupal database copy tasks to better support copying from one server to another. --- README.md | 4 +- cms/drupal/8.php | 6 ++- cms/drupal/db/backup/copy.php | 66 +++++++++++++++++++------- cms/drupal/db/backup/destination.php | 15 ++---- cms/drupal/db/copy.php | 64 +++++++++++++++++++++++++ cms/drupal/db/pull.yml | 11 ----- cms/drupal/db/update.php | 1 + cms/wp/5.php | 5 +- composer.json | 2 +- config.php | 1 + db/backup/copy.php | 23 +++++++++ env/check.php | 10 ++++ src/VirtualMachine/ClientInterface.php | 11 ++++- src/VirtualMachine/Ddev/Client.php | 11 ++++- src/VirtualMachine/Docksal/Client.php | 4 +- src/VirtualMachine/Vagrant/Client.php | 11 ++++- src/functions.php | 3 +- 17 files changed, 196 insertions(+), 52 deletions(-) create mode 100644 cms/drupal/db/copy.php delete mode 100644 cms/drupal/db/pull.yml create mode 100644 db/backup/copy.php create mode 100644 env/check.php diff --git a/README.md b/README.md index 2e54b9e..9963c09 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,11 @@ Please note that tasks assume databases on relevant stages have already been configured. If you need to skip all database operations, you can set `skip_db_ops` to `true` [via the command line](https://deployer.org/docs/7.x/cli#overriding-configuration-options). -Run `vendor/bin/dep tree deploy` to view the `deploy` recipe tree. +Run `vendor/bin/dep tree deploy` to view the `deploy` task tree. Run `vendor/bin/dep deploy` to deploy. -Run `vendor/bin/dep` to review available recipes. +Run `vendor/bin/dep` to review available tasks. ### Before/After Hooks Deployer supports running tasks before or after other defined tasks. Defining diff --git a/cms/drupal/8.php b/cms/drupal/8.php index 1870c6d..89daa8c 100644 --- a/cms/drupal/8.php +++ b/cms/drupal/8.php @@ -23,6 +23,8 @@ ]); fill('shared_file_names', [ '.env', + '.env.local', + // @todo remove the following three lines once confirmed no longer necessary '{{app_directory_name}}/sites/{{site}}/config.local.php', '{{app_directory_name}}/sites/{{site}}/databases.local.php', '{{app_directory_name}}/sites/{{site}}/settings.local.php', @@ -38,10 +40,9 @@ config_backup(); import('recipe/drupal8.php'); config_backup_merge(); -import('vendor/unleashedtech/deployer-recipes/db/backup/download.php'); import('vendor/unleashedtech/deployer-recipes/cms/drupal/db/backup/create.php'); import('vendor/unleashedtech/deployer-recipes/cms/drupal/db/backup/import.php'); -import('vendor/unleashedtech/deployer-recipes/cms/drupal/db/pull.yml'); +import('vendor/unleashedtech/deployer-recipes/cms/drupal/db/copy.php'); import('vendor/unleashedtech/deployer-recipes/cms/drupal/vars/vars.yml'); import('vendor/unleashedtech/deployer-recipes/cms/drupal/post/deploy.yml'); import('vendor/unleashedtech/deployer-recipes/cms/drupal/pre/deploy.yml'); @@ -50,6 +51,7 @@ // Create the "deploy" task. task('deploy', [ + 'cms:drupal:db:backup:create', 'cms:drupal:db:backup:create', 'cms:drupal:vars', 'deploy:prepare', diff --git a/cms/drupal/db/backup/copy.php b/cms/drupal/db/backup/copy.php index 4eb2135..3d13493 100644 --- a/cms/drupal/db/backup/copy.php +++ b/cms/drupal/db/backup/copy.php @@ -10,30 +10,64 @@ namespace Deployer; -use UnleashedTech\DeployerRecipes\VirtualMachine\VirtualMachine; +use Deployer\Host\Host; task('cms:drupal:db:backup:copy', static function (): void { - // Get the destination host. - $host = get('backup_destination_host'); - if (! $host) { - throw new \UnexpectedValueException('Backup destination host required.'); + // Get the environments the file(s) should be copied to. + $environments = get('environments_copy_db_files'); + if (! \count($environments)) { + error('Destination environments required.'); } - // Copy it to the destination host. - if (! \in_array($host, VirtualMachine::getNames(), true)) { - // TODO: transfer it to the destination host - throw new \UnexpectedValueException('Host to host file transfer is not supported yet.'); - } - - // TODO: transfer directly into the VM via scp instead of using runLocally - runLocally('mkdir -p {{local_database_backups}}'); + // Get the list of files to copy. + $files = []; $sites = get('sites'); if (! \is_array($sites)) { $sites = \explode(',', $sites); } foreach ($sites as $site) { - $file = run('\ls -rt -1 {{backups}} | grep "' . $site . '" | tail -1'); - download('{{backups}}/' . $file, '{{local_database_backups}}/'); + $files[] = run('\ls -rt -1 {{backups}} | grep "' . $site . '" | tail -1'); + } + + // Copy the files to the destination environment(s). + foreach ($environments as $environment) { + if ($environment === 'local') { + // TODO: transfer directly into the VM via scp instead of using runLocally + runLocally('mkdir -p {{local_database_backups}}'); + foreach ($files as $file) { + download('{{backups}}/' . $file, '{{local_database_backups}}/'); + } + } else { + $currentHost = currentHost(); + foreach (Deployer::get()->hosts as $destHost) { + \assert($destHost instanceof Host); + if ($environment !== $destHost->getLabels()['stage']) { + continue; + } + + foreach ($files as $file) { + $sourceFilename = '{{backups}}/' . $file; + $destFilename = '{{backups}}/' . $file; + // Only copy the file if it does not already exist at the destination. + $exists = (bool) runLocally(\vsprintf('ssh %s [ -f %s ] && echo 1 || echo 0', [ + $destHost->getHostname(), + $destFilename, + ])); + if ($exists) { + continue; + } + + info(\vsprintf('Copying "%s" to "%s".', [ + $file, + $destHost->getHostname(), + ])); + runLocally(\vsprintf('scp -3 %s %s', [ + $currentHost->getHostname() . ':' . $sourceFilename, + $destHost->getHostname() . ':' . $destFilename, + ])); + } + } + } } -})->desc('Download the latest database backup.')->once(); +})->desc('Copy the latest database backup(s).')->once(); diff --git a/cms/drupal/db/backup/destination.php b/cms/drupal/db/backup/destination.php index d082e56..dc1c737 100644 --- a/cms/drupal/db/backup/destination.php +++ b/cms/drupal/db/backup/destination.php @@ -14,23 +14,14 @@ use UnleashedTech\DeployerRecipes\VirtualMachine\VirtualMachine; task('cms:drupal:db:backup:destination', static function (): void { - // Build the list of host choices. - $choices = [ + $backupDestinationHostOptions = [ VirtualMachine::load('drupal')->getName(), ]; - $protected = get('environments_protected'); - if (! \is_array($protected)) { - $protected = \explode(',', $protected); - } - foreach (Deployer::get()->hosts as $host) { /** @var Host $host */ - $stage = $host->getLabels()['stage']; - if (! \in_array($stage, $protected, true)) { - $choices[] = $host->getHostname(); - } + $backupDestinationHostOptions[] = $host->getHostname(); } // Prompt which host the db backup should be imported on. - set('backup_destination_host', askChoice('Which host should the exported database(s) be imported on?', $choices, 0)); + set('backup_destination_host', askChoice('Which host should the exported database(s) be copied to?', $backupDestinationHostOptions, 0)); })->desc('Prompt for the database backup destination host.')->once(); diff --git a/cms/drupal/db/copy.php b/cms/drupal/db/copy.php new file mode 100644 index 0000000..49d7cd0 --- /dev/null +++ b/cms/drupal/db/copy.php @@ -0,0 +1,64 @@ +hosts as $host) { + \assert($host instanceof Host); + if (! $host->has('labels')) { + continue; + } + + $environment = $host->getLabels()['stage']; + if ($environment !== currentHost()->getLabels()['stage']) { + $destEnvironmentsOptions[] = $environment; + } + } + + $destEnvironments = askChoice('Which environment(s) should the exported database(s) be copied to? (comma separated)', \array_unique($destEnvironmentsOptions), 0, true); + set('environments_copy_db_files', $destEnvironments); + + // Prompt whether the db backup(s) should be imported. + $protectedEnvironments = get('environments_protected'); + if (! \is_array($protectedEnvironments)) { + $protectedEnvironments = \explode(',', $protectedEnvironments ?? ''); + } + + $environmentsToImportDbFiles = []; + foreach ($destEnvironments as $destEnvironment) { + if (\in_array($destEnvironment, $protectedEnvironments, true)) { + info(\vsprintf('Since the "%s" environment is protected, I won\'t ask about importing the DB there...', [ + $destEnvironment, + ])); + } else { + $response = askChoice(\vsprintf('Would you like to import the database file(s) once copied to the "%s" environment?', [ + $destEnvironment, + ]), ['No', 'Yes'], 0); + if ($response === 'Yes') { + $environmentsToImportDbFiles[] = $destEnvironment; + } + } + } + + set('environments_import_db_files', $environmentsToImportDbFiles); + + // Run the related tasks. + invoke('cms:drupal:db:backup:create'); + invoke('cms:drupal:db:backup:copy'); +})->desc('Prompt for the database backup destination host.')->once(); diff --git a/cms/drupal/db/pull.yml b/cms/drupal/db/pull.yml deleted file mode 100644 index c7a5009..0000000 --- a/cms/drupal/db/pull.yml +++ /dev/null @@ -1,11 +0,0 @@ -import: - - 'vendor/unleashedtech/deployer-recipes/cms/drupal/db/backup/destination.php' - - 'vendor/unleashedtech/deployer-recipes/cms/drupal/db/backup/copy.php' - -tasks: - cms:drupal:db:pull: - - cms:drupal:db:backup:destination - - cms:drupal:db:backup:create - - cms:drupal:db:backup:copy - - cms:drupal:db:backup:import - - db:backup:cleanup diff --git a/cms/drupal/db/update.php b/cms/drupal/db/update.php index 329e41d..5263c8d 100644 --- a/cms/drupal/db/update.php +++ b/cms/drupal/db/update.php @@ -24,6 +24,7 @@ foreach ($sites as $site) { within($appPath . '/sites/' . $site, static function () use ($timeout): void { + // TODO: this task should immediately fail if one of the db updates fails (e.g. it shouldn't continue to the next site) run('{{drush}} updb -y', [], $timeout); }); } diff --git a/cms/wp/5.php b/cms/wp/5.php index 023d903..30ab7c6 100644 --- a/cms/wp/5.php +++ b/cms/wp/5.php @@ -15,7 +15,10 @@ // Conditionally apply WordPress-specific defaults. set('app_type', 'wordpress'); fill('upload_dir', 'web/app/uploads'); -fill('shared_files', ['.env']); +fill('shared_files', [ + '.env', + '.env.local', +]); fill('shared_dirs', ['{{upload_dir}}']); fill('writable_dirs', ['{{upload_dir}}']); fill('wp', '{{bin}}/wp'); diff --git a/composer.json b/composer.json index c187c51..b4c56e2 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "source": "https://github.com/unleashedtech/deployer-recipes" }, "require": { - "php": "^7.3 || ^8.0", + "php": "^7.4 || ^8.0", "deployer/dist": "7.0.0-rc.4" }, "minimum-stability": "dev", diff --git a/config.php b/config.php index 554bdfd..c8a5d2c 100644 --- a/config.php +++ b/config.php @@ -13,6 +13,7 @@ require_once __DIR__ . '/src/functions.php'; import('vendor/unleashedtech/deployer-recipes/dev/sites.php'); +import('vendor/unleashedtech/deployer-recipes/db/backup/copy.php'); // Conditionally apply global defaults. fill('app_path', '{{release}}/{{app_directory_name}}'); diff --git a/db/backup/copy.php b/db/backup/copy.php new file mode 100644 index 0000000..cf13a78 --- /dev/null +++ b/db/backup/copy.php @@ -0,0 +1,23 @@ +desc('Copy database(s) to various environments.')->once(); diff --git a/env/check.php b/env/check.php new file mode 100644 index 0000000..0dace2d --- /dev/null +++ b/env/check.php @@ -0,0 +1,10 @@ +