Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Add scp transfer method #215

Merged
merged 8 commits into from
Feb 16, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions Documentation/ApiReference/Task/Transfer/ScpTask.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
------------------------------------
TYPO3\\Surf\\Task\\Transfer\\ScpTask
------------------------------------

.. php:namespace: TYPO3\\Surf\\Task\\Transfer

.. php:class:: ScpTask

A scp transfer task

Copies the application assets from the application workspace to the node using scp.

.. php:attr:: shell

protected ShellCommandService

.. php:method:: execute(Node $node, Application $application, Deployment $deployment, $options = [])

:type $node: Node
:param $node:
:type $application: Application
:param $application:
:type $deployment: Deployment
:param $deployment:
:type $options: array
:param $options:

.. php:method:: simulate(Node $node, Application $application, Deployment $deployment, $options = [])

Simulate this task

:type $node: Node
:param $node:
:type $application: Application
:param $application:
:type $deployment: Deployment
:param $deployment:
:type $options: array
:param $options:

.. php:method:: rollback(Node $node, Application $application, Deployment $deployment, $options = [])

Rollback this task

:type $node: Node
:param $node:
:type $application: Application
:param $application:
:type $deployment: Deployment
:param $deployment:
:type $options: array
:param $options:

.. php:method:: getExcludes($options)

:type $options: array
:param $options:
:returns: string

.. php:method:: setShellCommandService(ShellCommandService $shellCommandService)

:type $shellCommandService: ShellCommandService
:param $shellCommandService:
1 change: 1 addition & 0 deletions Documentation/ApiReference/Task/Transfer/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ TYPO3\\Surf\\Task\\Transfer
.. toctree::

RsyncTask
ScpTask
78 changes: 78 additions & 0 deletions Tests/Unit/Task/Transfer/ScpTaskTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

namespace TYPO3\Surf\Tests\Unit\Task\Transfer;

/*
* This file is part of TYPO3 Surf.
*
* For the full copyright and license information, please view the LICENSE.txt
* file that was distributed with this source code.
*/

use TYPO3\Surf\Application\Neos\Flow;
use TYPO3\Surf\Task\Transfer\ScpTask;
use TYPO3\Surf\Tests\Unit\Task\BaseTaskTest;

class ScpTaskTest extends BaseTaskTest
{

/**
* Set up test dependencies
*/
protected function setUp()
{
parent::setUp();
$this->application = new Flow('TestApplication');
$this->application->setDeploymentPath('/home/jdoe/app');
}

/**
* @return \TYPO3\Surf\Domain\Model\Task
*/
protected function createTask()
{
return new ScpTask();
}

/**
* @test
*/
public function executeWithoutExcludes()
{
$this->node->setOption('hostname', 'myserver.local');
$this->node->setOption('username', 'jdoe');

$releaseIdentifier = $this->deployment->getReleaseIdentifier();

$expectedCommands = [
'mkdir -p /home/jdoe/app/cache/transfer',
'rm -rf ./Data/Surf/TestDeployment/TestApplication/*.tar.gz',
sprintf('cd ./Data/Surf/TestDeployment/TestApplication/; tar --exclude=".git" --exclude="%1$s.tar.gz" -zcf %1$s.tar.gz -C ./Data/Surf/TestDeployment/TestApplication .', $releaseIdentifier),
sprintf('scp ./Data/Surf/TestDeployment/TestApplication/%s.tar.gz [email protected]:/home/jdoe/app/cache/transfer', $releaseIdentifier),
sprintf('tar -zxvf /home/jdoe/app/cache/transfer/%1$s.tar.gz -C /home/jdoe/app/releases/%1$s', $releaseIdentifier),
sprintf('rm -rf /home/jdoe/app/cache/transfer/%s.tar.gz', $releaseIdentifier),
sprintf('rm -rf ./Data/Surf/TestDeployment/TestApplication/%s.tar.gz', $releaseIdentifier),
];

$this->task->execute($this->node, $this->application, $this->deployment, []);

foreach ($expectedCommands as $expectedCommand) {
$this->assertCommandExecuted($expectedCommand);
}
}

/**
* @test
*/
public function executeWithAdditionalExcludes()
{
$this->node->setOption('hostname', 'myserver.local');
$this->node->setOption('username', 'jdoe');

$releaseIdentifier = $this->deployment->getReleaseIdentifier();

$this->task->execute($this->node, $this->application, $this->deployment, ['scpExcludes' => ['file.txt']]);

$this->assertCommandExecuted(sprintf('cd ./Data/Surf/TestDeployment/TestApplication/; tar --exclude=".git" --exclude="%1$s.tar.gz" --exclude="file.txt" -zcf %1$s.tar.gz -C ./Data/Surf/TestDeployment/TestApplication .', $releaseIdentifier));
}
}
3 changes: 2 additions & 1 deletion src/Application/BaseApplication.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use TYPO3\Surf\Task\Package\GitTask;
use TYPO3\Surf\Task\SymlinkReleaseTask;
use TYPO3\Surf\Task\Transfer\RsyncTask;
use TYPO3\Surf\Task\Transfer\ScpTask;
use TYPO3\Surf\Task\UnlockDeploymentTask;

/**
Expand Down Expand Up @@ -278,7 +279,7 @@ protected function registerTasksForTransferMethod(Workflow $workflow, $transferM
$workflow->addTask(RsyncTask::class, 'transfer', $this);
break;
case 'scp':
// TODO
$workflow->addTask(ScpTask::class, 'transfer', $this);
break;
}
}
Expand Down
156 changes: 156 additions & 0 deletions src/Task/Transfer/ScpTask.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<?php

namespace TYPO3\Surf\Task\Transfer;

/*
* This file is part of TYPO3 Surf.
*
* For the full copyright and license information, please view the LICENSE.txt
* file that was distributed with this source code.
*/

use TYPO3\Flow\Utility\Files;
use TYPO3\Surf\Domain\Model\Application;
use TYPO3\Surf\Domain\Model\Deployment;
use TYPO3\Surf\Domain\Model\Node;
use TYPO3\Surf\Domain\Model\Task;
use TYPO3\Surf\Domain\Service\ShellCommandServiceAwareInterface;
use TYPO3\Surf\Domain\Service\ShellCommandServiceAwareTrait;
use TYPO3\Surf\Exception\InvalidConfigurationException;
use TYPO3\Surf\Exception\TaskExecutionException;

/**
* A scp transfer task
*
* Copies the application assets from the application workspace to the node using scp.
*/
final class ScpTask extends Task implements ShellCommandServiceAwareInterface
{
use ShellCommandServiceAwareTrait;

/**
* @param Node $node
* @param Application $application
* @param Deployment $deployment
* @param array $options
*
* @throws InvalidConfigurationException
* @throws TaskExecutionException
*/
public function execute(Node $node, Application $application, Deployment $deployment, array $options = [])
{
$fileName = sprintf('%s.tar.gz', $deployment->getReleaseIdentifier());

$localPackagePath = $deployment->getWorkspacePath($application);
$releasePath = Files::concatenatePaths([$application->getReleasesPath(), $deployment->getReleaseIdentifier()]);

// Create remote transfer path if not exist
$remoteTransferPath = Files::concatenatePaths([$application->getDeploymentPath(), 'cache', 'transfer']);
$this->shell->executeOrSimulate(sprintf('mkdir -p %s', $remoteTransferPath), $node, $deployment);

// Create the scp destination command
$destinationArgument = $node->isLocalhost()
? $remoteTransferPath
: sprintf(
'%s@%s:%s',
$node->getUsername(),
$node->getHostname(),
$remoteTransferPath
);

// escape whitespaces
$localPackagePath = preg_replace('/\s+/', '\ ', $localPackagePath);
$destinationArgument = preg_replace('/\s+/', '\ ', $destinationArgument);

// Create a localhost node and create tarball on it
$localhost = new Node('localhost');
$localhost->onLocalhost();
// To prevent issues remove all tar.gz in it before
$this->shell->executeOrSimulate(sprintf('rm -rf %s/*.tar.gz', $localPackagePath), $localhost, $deployment);
// Create empty tarball to avoid warning that "file changed as we read it"
$this->shell->executeOrSimulate(sprintf('cd %s/; touch %s', $localPackagePath, $fileName), $localhost, $deployment);
// Create tarball locally in the workspace directory
$this->shell->executeOrSimulate(
sprintf(
'cd %1$s/; tar %3$s -czf %2$s -C %1$s .',
$localPackagePath,
$fileName,
$this->getExcludes($options, $fileName)
),
$localhost,
$deployment,
false,
false
);

// Transfer tarball to target server
$this->shell->executeOrSimulate(
sprintf('scp %s/%s %s', $localPackagePath, $fileName, $destinationArgument),
$localhost,
$deployment
);

// Extract tarball on server in release path
$this->shell->executeOrSimulate(
sprintf('tar -xzf %s/%s -C %s', $remoteTransferPath, $fileName, $releasePath),
$node,
$deployment
);

// Delete tarball on localhost and Server
$this->shell->executeOrSimulate(sprintf('rm -f %s/%s', $remoteTransferPath, $fileName), $node, $deployment);
$this->shell->executeOrSimulate(sprintf('rm -f %s/%s', $localPackagePath, $fileName), $localhost, $deployment);
}

/**
* Simulate this task
*
* @param Node $node
* @param Application $application
* @param Deployment $deployment
* @param array $options
*
* @throws InvalidConfigurationException
* @throws TaskExecutionException
*/
public function simulate(Node $node, Application $application, Deployment $deployment, array $options = [])
{
$this->execute($node, $application, $deployment, $options);
}

/**
* Rollback this task
*
* @param Node $node
* @param Application $application
* @param Deployment $deployment
* @param array $options
*
* @throws TaskExecutionException
*/
public function rollback(Node $node, Application $application, Deployment $deployment, array $options = [])
{
$releasePath = $deployment->getApplicationReleasePath($application);
$this->shell->execute(sprintf('rm -rf %s', $releasePath), $node, $deployment, true);
}

/**
* @param array $options
* @param string $fileName
*
* @return string
*/
private function getExcludes(array $options, $fileName)
{
$excludes = ['.git', $fileName];
if (isset($options['scpExcludes']) && is_array($options['scpExcludes'])) {
$excludes = array_merge($excludes, array_filter($options['scpExcludes']));
}

foreach ($excludes as &$exclude) {
$exclude = '--exclude="' . $exclude . '"';
}

return implode(' ', $excludes);
}
}