-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
558f648
commit c66db26
Showing
40 changed files
with
3,102 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
<?php | ||
|
||
$fileHeaderComment = <<<COMMENT | ||
This file is part of the EasyDeploy project. | ||
(c) Javier Eguiluz <[email protected]> | ||
For the full copyright and license information, please view the LICENSE | ||
file that was distributed with this source code. | ||
COMMENT; | ||
|
||
return PhpCsFixer\Config::create() | ||
->setRiskyAllowed(true) | ||
->setRules([ | ||
'@Symfony' => true, | ||
'@Symfony:risky' => true, | ||
'array_syntax' => ['syntax' => 'short'], | ||
'header_comment' => ['header' => $fileHeaderComment, 'separate' => 'both'], | ||
'linebreak_after_opening_tag' => true, | ||
'list_syntax' => ['syntax' => 'short'], | ||
'mb_str_functions' => true, | ||
'no_php4_constructor' => true, | ||
'no_unreachable_default_argument_value' => true, | ||
'no_useless_else' => true, | ||
'no_useless_return' => true, | ||
'ordered_class_elements' => true, | ||
'ordered_imports' => true, | ||
'php_unit_strict' => true, | ||
'phpdoc_order' => true, | ||
'pow_to_exponentiation' => true, | ||
'random_api_migration' => true, | ||
'return_type_declaration' => ['space_before' => 'one'], | ||
'semicolon_after_instruction' => true, | ||
'strict_comparison' => true, | ||
'strict_param' => true, | ||
'ternary_to_null_coalescing' => true, | ||
]) | ||
->setCacheFile(__DIR__.'/.php_cs.cache') | ||
; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
|
||
<!-- https://phpunit.de/manual/current/en/appendixes.configuration.html --> | ||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.8/phpunit.xsd" | ||
backupGlobals="false" | ||
colors="true" | ||
bootstrap="vendor/autoload.php" | ||
> | ||
<php> | ||
<ini name="error_reporting" value="-1" /> | ||
<server name="KERNEL_DIR" value="src/" /> | ||
</php> | ||
|
||
<testsuites> | ||
<testsuite name="Project Test Suite"> | ||
<directory>tests/</directory> | ||
</testsuite> | ||
</testsuites> | ||
</phpunit> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the EasyDeploy project. | ||
* | ||
* (c) Javier Eguiluz <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace EasyCorp\Bundle\EasyDeployBundle\Command; | ||
|
||
use EasyCorp\Bundle\EasyDeployBundle\Context; | ||
use EasyCorp\Bundle\EasyDeployBundle\Exception\SymfonyVersionException; | ||
use Symfony\Component\Console\Command\Command; | ||
use Symfony\Component\Console\Input\InputArgument; | ||
use Symfony\Component\Console\Input\InputInterface; | ||
use Symfony\Component\Console\Input\InputOption; | ||
use Symfony\Component\Console\Output\OutputInterface; | ||
use Symfony\Component\Console\Question\ConfirmationQuestion; | ||
use Symfony\Component\Filesystem\Filesystem; | ||
use Symfony\Component\HttpKernel\Config\FileLocator; | ||
use Symfony\Component\HttpKernel\Kernel; | ||
|
||
class DeployCommand extends Command | ||
{ | ||
private $fileLocator; | ||
private $projectDir; | ||
private $logDir; | ||
private $configFilePath; | ||
|
||
public function __construct(FileLocator $fileLocator, string $projectDir, string $logDir) | ||
{ | ||
$this->fileLocator = $fileLocator; | ||
$this->projectDir = realpath($projectDir); | ||
$this->logDir = $logDir; | ||
|
||
parent::__construct(); | ||
} | ||
|
||
protected function configure() | ||
{ | ||
$this | ||
->setName('deploy') | ||
->setDescription('Deploys a Symfony application to one or more remote servers.') | ||
->setHelp('...') | ||
->addArgument('stage', InputArgument::OPTIONAL, 'The stage to deploy to ("production", "staging", etc.)', 'prod') | ||
->addOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Load configuration from the given file path') | ||
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Shows the commands to perform the deployment without actually executing them') | ||
; | ||
} | ||
|
||
protected function initialize(InputInterface $input, OutputInterface $output) | ||
{ | ||
$customConfigPath = $input->getOption('configuration'); | ||
if (null !== $customConfigPath && !is_readable($customConfigPath)) { | ||
throw new \RuntimeException(sprintf("The given configuration file ('%s') does not exist or it's not readable.", $customConfigPath)); | ||
} | ||
|
||
if (null !== $customConfigPath && is_readable($customConfigPath)) { | ||
return $this->configFilePath = $customConfigPath; | ||
} | ||
|
||
$defaultConfigPath = $this->getDefaultConfigPath($input->getArgument('stage')); | ||
if (is_readable($defaultConfigPath)) { | ||
return $this->configFilePath = $defaultConfigPath; | ||
} | ||
|
||
$this->createDefaultConfigFile($input, $output, $defaultConfigPath, $input->getArgument('stage')); | ||
} | ||
|
||
protected function execute(InputInterface $input, OutputInterface $output) | ||
{ | ||
$logFilePath = sprintf('%s/deploy_%s.log', $this->logDir, $input->getArgument('stage')); | ||
$context = new Context($input, $output, $this->projectDir, $logFilePath, true === $input->getOption('dry-run'), $output->isVerbose()); | ||
|
||
$deployer = include $this->configFilePath; | ||
$deployer->initialize($context); | ||
$deployer->doDeploy(); | ||
} | ||
|
||
private function getDefaultConfigPath(string $stageName) : string | ||
{ | ||
$symfonyVersion = Kernel::MAJOR_VERSION; | ||
$defaultConfigPaths = [ | ||
2 => sprintf('%s/app/config/deploy_%s.php', $this->projectDir, $stageName), | ||
3 => sprintf('%s/app/config/deploy_%s.php', $this->projectDir, $stageName), | ||
4 => sprintf('%s/etc/%s/deploy.php', $this->projectDir, $stageName), | ||
]; | ||
|
||
if (!isset($defaultConfigPaths[$symfonyVersion])) { | ||
throw new SymfonyVersionException($symfonyVersion); | ||
} | ||
|
||
return $defaultConfigPaths[$symfonyVersion]; | ||
} | ||
|
||
private function createDefaultConfigFile(InputInterface $input, OutputInterface $output, string $defaultConfigPath, string $stageName) : void | ||
{ | ||
$helper = $this->getHelper('question'); | ||
$question = new ConfirmationQuestion(sprintf("\n<bg=yellow> WARNING </> There is no config file to deploy '%s' stage.\nDo you want to create a minimal config file for it? [Y/n] ", $stageName), true); | ||
|
||
if (!$helper->ask($input, $output, $question)) { | ||
$output->writeln(sprintf('<fg=green>OK</>, but before running this command again, create this config file: %s', $defaultConfigPath)); | ||
} else { | ||
(new Filesystem())->copy($this->fileLocator->locate('@EasyDeployBundle/Resources/skeleton/deploy.php.dist'), $defaultConfigPath); | ||
$output->writeln(sprintf('<fg=green>OK</>, now edit the "%s" config file and run this command again.', $defaultConfigPath)); | ||
} | ||
|
||
exit(0); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the EasyDeploy project. | ||
* | ||
* (c) Javier Eguiluz <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace EasyCorp\Bundle\EasyDeployBundle\Command; | ||
|
||
use EasyCorp\Bundle\EasyDeployBundle\Context; | ||
use EasyCorp\Bundle\EasyDeployBundle\Exception\SymfonyVersionException; | ||
use Symfony\Component\Console\Command\Command; | ||
use Symfony\Component\Console\Input\InputArgument; | ||
use Symfony\Component\Console\Input\InputInterface; | ||
use Symfony\Component\Console\Input\InputOption; | ||
use Symfony\Component\Console\Output\OutputInterface; | ||
use Symfony\Component\HttpKernel\Kernel; | ||
|
||
class RollbackCommand extends Command | ||
{ | ||
private $projectDir; | ||
private $logDir; | ||
private $configFilePath; | ||
|
||
public function __construct(string $projectDir, string $logDir) | ||
{ | ||
$this->projectDir = $projectDir; | ||
$this->logDir = $logDir; | ||
|
||
parent::__construct(); | ||
} | ||
|
||
protected function configure() | ||
{ | ||
$this | ||
->setName('rollback') | ||
->setDescription('Deploys a Symfony application to one or more remote servers.') | ||
->setHelp('...') | ||
->addArgument('stage', InputArgument::OPTIONAL, 'The stage to roll back ("production", "staging", etc.)', 'prod') | ||
->addOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Load configuration from the given file path') | ||
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Shows the commands to perform the roll back without actually executing them') | ||
; | ||
} | ||
|
||
protected function initialize(InputInterface $input, OutputInterface $output) | ||
{ | ||
$customConfigPath = $input->getOption('configuration'); | ||
if (null !== $customConfigPath && !is_readable($customConfigPath)) { | ||
throw new \RuntimeException(sprintf("The given configuration file ('%s') does not exist or it's not readable.", $customConfigPath)); | ||
} | ||
|
||
if (null !== $customConfigPath && is_readable($customConfigPath)) { | ||
return $this->configFilePath = $customConfigPath; | ||
} | ||
|
||
$defaultConfigPath = $this->getDefaultConfigPath($input->getArgument('stage')); | ||
if (is_readable($defaultConfigPath)) { | ||
return $this->configFilePath = $defaultConfigPath; | ||
} | ||
|
||
throw new \RuntimeException(sprintf("The default configuration file does not exist or it's not readable, and no custom configuration file was given either. Create the '%s' configuration file and run this command again.", $defaultConfigPath)); | ||
} | ||
|
||
protected function execute(InputInterface $input, OutputInterface $output) | ||
{ | ||
$logFilePath = sprintf('%s/deploy_%s.log', $this->logDir, $input->getArgument('stage')); | ||
$context = new Context($input, $output, $this->projectDir, $logFilePath, true === $input->getOption('dry-run'), $output->isVerbose()); | ||
|
||
$deployer = include $this->configFilePath; | ||
$deployer->initialize($context); | ||
$deployer->doRollback(); | ||
} | ||
|
||
private function getDefaultConfigPath(string $stageName) : string | ||
{ | ||
$symfonyVersion = Kernel::MAJOR_VERSION; | ||
$defaultConfigPaths = [ | ||
2 => sprintf('%s/app/config/deploy_%s.php', $this->projectDir, $stageName), | ||
3 => sprintf('%s/app/config/deploy_%s.php', $this->projectDir, $stageName), | ||
4 => sprintf('%s/etc/%s/deploy.php', $this->projectDir, $stageName), | ||
]; | ||
|
||
if (!isset($defaultConfigPaths[$symfonyVersion])) { | ||
throw new SymfonyVersionException($symfonyVersion); | ||
} | ||
|
||
return $defaultConfigPaths[$symfonyVersion]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the EasyDeploy project. | ||
* | ||
* (c) Javier Eguiluz <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace EasyCorp\Bundle\EasyDeployBundle\Configuration; | ||
|
||
use EasyCorp\Bundle\EasyDeployBundle\Exception\InvalidConfigurationException; | ||
use EasyCorp\Bundle\EasyDeployBundle\Server\Property; | ||
use EasyCorp\Bundle\EasyDeployBundle\Server\Server; | ||
use EasyCorp\Bundle\EasyDeployBundle\Server\ServerRepository; | ||
|
||
/** | ||
* It implements the "Builder" pattern to define the configuration of the deployer. | ||
* This is the base builder extended by the specific builder used by each deployer. | ||
*/ | ||
abstract class AbstractConfiguration | ||
{ | ||
private const RESERVED_SERVER_PROPERTIES = [Property::use_ssh_agent_forwarding]; | ||
protected $servers; | ||
protected $useSshAgentForwarding = true; | ||
|
||
public function __construct() | ||
{ | ||
$this->servers = new ServerRepository(); | ||
} | ||
|
||
public function server(string $sshDsn, array $roles = [Server::ROLE_APP], array $properties = []) | ||
{ | ||
$reservedProperties = array_merge(self::RESERVED_SERVER_PROPERTIES, $this->getReservedServerProperties()); | ||
$reservedPropertiesUsed = array_intersect($reservedProperties, array_keys($properties)); | ||
if (!empty($reservedPropertiesUsed)) { | ||
throw new InvalidConfigurationException(sprintf('These properties set for the "%s" server are reserved: %s. Use different property names.', $sshDsn, implode(', ', $reservedPropertiesUsed))); | ||
} | ||
|
||
$this->servers->add(new Server($sshDsn, $roles, $properties)); | ||
} | ||
|
||
public function useSshAgentForwarding(bool $useIt) | ||
{ | ||
$this->useSshAgentForwarding = $useIt; | ||
} | ||
|
||
abstract protected function getReservedServerProperties() : array; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the EasyDeploy project. | ||
* | ||
* (c) Javier Eguiluz <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace EasyCorp\Bundle\EasyDeployBundle\Configuration; | ||
|
||
use EasyCorp\Bundle\EasyDeployBundle\Helper\Str; | ||
use Symfony\Component\HttpFoundation\ParameterBag; | ||
|
||
/** | ||
* It implements the "Adapter" pattern to allow working with the configuration | ||
* in a consistent manner, even if the configuration of each deployer is | ||
* completely different and defined using incompatible objects. | ||
*/ | ||
final class ConfigurationAdapter | ||
{ | ||
private $config; | ||
/** @var ParameterBag */ | ||
private $options; | ||
|
||
public function __construct(AbstractConfiguration $config) | ||
{ | ||
$this->config = $config; | ||
} | ||
|
||
public function __toString() : string | ||
{ | ||
return Str::formatAsTable($this->getOptions()->all()); | ||
} | ||
|
||
public function get(string $optionName) | ||
{ | ||
if (!$this->getOptions()->has($optionName)) { | ||
throw new \InvalidArgumentException(sprintf('The "%s" option is not defined.', $optionName)); | ||
} | ||
|
||
return $this->getOptions()->get($optionName); | ||
} | ||
|
||
private function getOptions() : ParameterBag | ||
{ | ||
if (null !== $this->options) { | ||
return $this->options; | ||
} | ||
|
||
// it's not the most beautiful code possible, but making the properties | ||
// private and the methods public allows to configure the deployment using | ||
// a config builder and the IDE autocompletion. Here we need to access | ||
// those private properties and their values | ||
$options = new ParameterBag(); | ||
$r = new \ReflectionObject($this->config); | ||
foreach ($r->getProperties() as $property) { | ||
try { | ||
$property->setAccessible(true); | ||
$options->set($property->getName(), $property->getValue($this->config)); | ||
} catch (\ReflectionException $e) { | ||
// ignore this error | ||
} | ||
} | ||
|
||
return $this->options = $options; | ||
} | ||
} |
Oops, something went wrong.