diff --git a/src/Commands/core/RsyncCommands.php b/src/Commands/core/RsyncCommands.php index a7946cb770..e582de3efa 100644 --- a/src/Commands/core/RsyncCommands.php +++ b/src/Commands/core/RsyncCommands.php @@ -69,14 +69,29 @@ public function rsync($source, $target, array $extra, $options = ['exclude-paths throw new UserAbortException(); } } - $rsync_options = $this->rsyncOptions($options); $parameters = array_merge([$rsync_options], $extra); - $parameters[] = Escape::shellArg($this->sourceEvaluatedPath->fullyQualifiedPathPreservingTrailingSlash()); - $parameters[] = Escape::shellArg($this->targetEvaluatedPath->fullyQualifiedPath()); - - $ssh_options = $this->getConfig()->get('ssh.options', ''); - $exec = "rsync -e 'ssh $ssh_options'" . ' ' . implode(' ', array_filter($parameters)); + $source_alias = $this->sourceEvaluatedPath->getSiteAlias(); + $target_alias = $this->targetEvaluatedPath->getSiteAlias(); + $remote_docker_alias = $source_alias->has('docker.host') ? $source_alias : ($target_alias->has('docker.host') ? $target_alias : null); + if ($remote_docker_alias) { + $docker_host = $remote_docker_alias->get('docker.host', ''); + $docker_project = $remote_docker_alias->get('docker.project', ''); + $parameters[] = $source_alias->has('docker.host') ? + $source_alias->get('docker.service') . ':' . Escape::shellArg($this->sourceEvaluatedPath->fullyQualifiedPathPreservingTrailingSlash()) : + Escape::shellArg($this->sourceEvaluatedPath->fullyQualifiedPathPreservingTrailingSlash()); + $parameters[] = $target_alias->has('docker.host') ? + $target_alias->get('docker.service') . ':' . Escape::shellArg($this->targetEvaluatedPath->fullyQualifiedPath()) : + Escape::shellArg($this->targetEvaluatedPath->fullyQualifiedPath()); + $compose_version = $remote_docker_alias->get('docker.compose.version', '1'); + $docker_e = $compose_version == '1' ? "docker-compose -H $docker_host -p $docker_project" : "docker -H $docker_host compose -p $docker_project"; + $exec = "rsync -e '$docker_e exec -i'" . ' ' . implode(' ', array_filter($parameters)); + } else { + $ssh_options = $this->getConfig()->get('ssh.options', ''); + $parameters[] = Escape::shellArg($this->sourceEvaluatedPath->fullyQualifiedPathPreservingTrailingSlash()); + $parameters[] = Escape::shellArg($this->targetEvaluatedPath->fullyQualifiedPath()); + $exec = "rsync -e 'ssh $ssh_options'" . ' ' . implode(' ', array_filter($parameters)); + } $process = $this->processManager()->shell($exec); $process->run($process->showRealtime()); diff --git a/src/Commands/sql/SqlSyncCommands.php b/src/Commands/sql/SqlSyncCommands.php index 84276ded6a..b14158fd95 100644 --- a/src/Commands/sql/SqlSyncCommands.php +++ b/src/Commands/sql/SqlSyncCommands.php @@ -171,7 +171,10 @@ public function rsync(array $options, SiteAlias $sourceRecord, SiteAlias $target // Determine path/to/dump on target. if ($options['target-dump']) { $target_dump_path = $options['target-dump']; - } elseif (!$sourceRecord->isRemote() && !$targetRecord->isRemote()) { + } elseif ( + !$sourceRecord->isRemote() && !$targetRecord->isRemote() + && (!$targetRecord->has('docker.host') && !$sourceRecord->has('docker.host')) + ) { $target_dump_path = $source_dump_path; $do_rsync = false; } else { diff --git a/tests/functional/SqlSyncTest.php b/tests/functional/SqlSyncTest.php index 8bb1377e3f..5b52909010 100644 --- a/tests/functional/SqlSyncTest.php +++ b/tests/functional/SqlSyncTest.php @@ -69,6 +69,57 @@ public function testSimulatedSqlSync() $this->assertStringContainsString("[notice] Simulating: ssh -o PasswordAuthentication=no user@server '/path/to/vendor/bin/drush --no-interaction sql:sync @synctest.remote @synctest.local --uri=sitename'", $output); } + public function testSimulatedDockerRemoteSqlSync() + { + if ($this->isWindows()) { + $this->markTestSkipped('On Windows, Paths mismatch and confuse rsync.'); + } + + $fixtureSites = [ + 'remote' => [ + 'docker' => [ + 'host' => 'ssh://www-admin@host', + 'service' => 'php' + ], + 'paths' => [ + 'drush-script' => '/path/to/drush', + ], + ], + 'local' => [ + ], + ]; + $this->setUpSettings($fixtureSites, 'synctest'); + $options = [ + 'uri' => 'OMIT', + 'simulate' => null, + 'alias-path' => __DIR__ . '/resources/alias-fixtures', + // @todo Ensure that shortcuts are normalized to long option names https://github.com/drush-ops/drush/pull/4515. + 'verbose' => null, + ]; + + $expectedAliasPath = '--alias-path=__DIR__/resources/alias-fixtures'; + + // Test simulated simple rsync remote-to-local + $this->drush(SqlSyncCommands::SYNC, ['@synctest.remote', '@synctest.local'], $options, '@synctest.local'); + $output = $this->getSimplifiedErrorOutput(); + $this->assertStringContainsString("[notice] Simulating: docker-compose exec -T php /path/to/drush sql:dump --no-interaction --strict=0 --gzip --result-file=auto --format=json --uri=remote", $output); + $this->assertStringContainsString("[notice] Simulating: __DIR__/drush.php core:rsync @synctest.remote:/simulated/path/to/dump.tgz @synctest.local:__SANDBOX__/tmp/dump.tgz --yes --uri=local -- --remove-source-files", $output); + $this->assertStringContainsString("[notice] Simulating: __DIR__/drush.php sql:query --no-interaction --strict=0 --file=__SANDBOX__/tmp/dump.tgz --file-delete --uri=local", $output); + + + // Test simulated simple sql:sync local-to-remote + $this->drush(SqlSyncCommands::SYNC, ['@synctest.local', '@synctest.remote'], $options, '@synctest.local'); + $output = $this->getSimplifiedErrorOutput(); + $this->assertStringContainsString("[notice] Simulating: __DIR__/drush.php sql:dump --no-interaction --strict=0 --gzip --result-file=auto --format=json --uri=local", $output); + $this->assertStringContainsString("[notice] Simulating: __DIR__/drush.php core:rsync @synctest.local:/simulated/path/to/dump.tgz @synctest.remote:/tmp/dump.tgz --yes --uri=local -- --remove-source-files", $output); + $this->assertStringContainsString("[notice] Simulating: docker-compose exec -T php /path/to/drush sql:query --no-interaction --strict=0 --file=/tmp/dump.tgz --file-delete --uri=remote", $output); + + // Test simulated remote invoke with a remote runner. + $this->drush(SqlSyncCommands::SYNC, ['@synctest.remote', '@synctest.local'], $options, 'user@server/path/to/drupal#sitename'); + $output = $this->getSimplifiedErrorOutput(); + $this->assertStringContainsString("[notice] Simulating: ssh -o PasswordAuthentication=no user@server '/path/to/vendor/bin/drush --no-interaction sql:sync @synctest.remote @synctest.local --uri=sitename'", $output); + } + /** * Covers the following responsibilities. * - A user created on the source site is copied to the destination site. diff --git a/tests/functional/resources/alias-fixtures/example.site.yml b/tests/functional/resources/alias-fixtures/example.site.yml index 1eb9557947..8fa4edca37 100644 --- a/tests/functional/resources/alias-fixtures/example.site.yml +++ b/tests/functional/resources/alias-fixtures/example.site.yml @@ -23,3 +23,19 @@ live: options: '-o PasswordAuthentication=example' paths: drush-script: '/example/path/to/drush' + + +stage-docker: + root: /path/to/stage + uri: stage + docker: + service: php + host: ssh://user@host + project: 'project' + compose: + version: 1 + command: + core: + rsync: + options: + exclude-paths: stage-path \ No newline at end of file diff --git a/tests/integration/RsyncIntegrationTest.php b/tests/integration/RsyncIntegrationTest.php index a441bf2c58..febbce27b7 100644 --- a/tests/integration/RsyncIntegrationTest.php +++ b/tests/integration/RsyncIntegrationTest.php @@ -38,6 +38,16 @@ public function testRsyncSimulated() $expected = "[notice] Simulating: rsync -e 'ssh ' -akz /path/to/stage /path/to/dev"; $this->assertErrorOutputContains($expected); + // Test source docker rsync with two local sites + $this->drush(RsyncCommands::RSYNC, ['@example.stage-docker', '@example.dev'], $options, self::EXIT_SUCCESS, ''); + $expected = "[notice] Simulating: rsync -e 'docker-compose -H ssh://user@host -p project exec -i' -akz php:/path/to/stage /path/to/dev"; + $this->assertErrorOutputContains($expected); + + // Test source docker rsync with two local sites + $this->drush(RsyncCommands::RSYNC, ['@example.dev', '@example.stage-docker'], $options, self::EXIT_SUCCESS, ''); + $expected = "[notice] Simulating: rsync -e 'docker-compose -H ssh://user@host -p project exec -i' -akz /path/to/dev php:/path/to/stage"; + $this->assertErrorOutputContains($expected); + // Test simulated rsync with relative paths $this->drush(RsyncCommands::RSYNC, ['@example.dev:files', '@example.stage:files'], $options, self::EXIT_SUCCESS, ''); $expected = "[notice] Simulating: rsync -e 'ssh ' -akz /path/to/dev/files /path/to/stage/files";