Skip to content

Commit c66db26

Browse files
committed
First code import
1 parent 558f648 commit c66db26

40 files changed

+3102
-2
lines changed

.php_cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
$fileHeaderComment = <<<COMMENT
4+
This file is part of the EasyDeploy project.
5+
6+
(c) Javier Eguiluz <javier.eguiluz@gmail.com>
7+
8+
For the full copyright and license information, please view the LICENSE
9+
file that was distributed with this source code.
10+
COMMENT;
11+
12+
return PhpCsFixer\Config::create()
13+
->setRiskyAllowed(true)
14+
->setRules([
15+
'@Symfony' => true,
16+
'@Symfony:risky' => true,
17+
'array_syntax' => ['syntax' => 'short'],
18+
'header_comment' => ['header' => $fileHeaderComment, 'separate' => 'both'],
19+
'linebreak_after_opening_tag' => true,
20+
'list_syntax' => ['syntax' => 'short'],
21+
'mb_str_functions' => true,
22+
'no_php4_constructor' => true,
23+
'no_unreachable_default_argument_value' => true,
24+
'no_useless_else' => true,
25+
'no_useless_return' => true,
26+
'ordered_class_elements' => true,
27+
'ordered_imports' => true,
28+
'php_unit_strict' => true,
29+
'phpdoc_order' => true,
30+
'pow_to_exponentiation' => true,
31+
'random_api_migration' => true,
32+
'return_type_declaration' => ['space_before' => 'one'],
33+
'semicolon_after_instruction' => true,
34+
'strict_comparison' => true,
35+
'strict_param' => true,
36+
'ternary_to_null_coalescing' => true,
37+
])
38+
->setCacheFile(__DIR__.'/.php_cs.cache')
39+
;

phpunit.xml.dist

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<!-- https://phpunit.de/manual/current/en/appendixes.configuration.html -->
4+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.8/phpunit.xsd"
6+
backupGlobals="false"
7+
colors="true"
8+
bootstrap="vendor/autoload.php"
9+
>
10+
<php>
11+
<ini name="error_reporting" value="-1" />
12+
<server name="KERNEL_DIR" value="src/" />
13+
</php>
14+
15+
<testsuites>
16+
<testsuite name="Project Test Suite">
17+
<directory>tests/</directory>
18+
</testsuite>
19+
</testsuites>
20+
</phpunit>

src/Command/DeployCommand.php

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the EasyDeploy project.
5+
*
6+
* (c) Javier Eguiluz <javier.eguiluz@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace EasyCorp\Bundle\EasyDeployBundle\Command;
13+
14+
use EasyCorp\Bundle\EasyDeployBundle\Context;
15+
use EasyCorp\Bundle\EasyDeployBundle\Exception\SymfonyVersionException;
16+
use Symfony\Component\Console\Command\Command;
17+
use Symfony\Component\Console\Input\InputArgument;
18+
use Symfony\Component\Console\Input\InputInterface;
19+
use Symfony\Component\Console\Input\InputOption;
20+
use Symfony\Component\Console\Output\OutputInterface;
21+
use Symfony\Component\Console\Question\ConfirmationQuestion;
22+
use Symfony\Component\Filesystem\Filesystem;
23+
use Symfony\Component\HttpKernel\Config\FileLocator;
24+
use Symfony\Component\HttpKernel\Kernel;
25+
26+
class DeployCommand extends Command
27+
{
28+
private $fileLocator;
29+
private $projectDir;
30+
private $logDir;
31+
private $configFilePath;
32+
33+
public function __construct(FileLocator $fileLocator, string $projectDir, string $logDir)
34+
{
35+
$this->fileLocator = $fileLocator;
36+
$this->projectDir = realpath($projectDir);
37+
$this->logDir = $logDir;
38+
39+
parent::__construct();
40+
}
41+
42+
protected function configure()
43+
{
44+
$this
45+
->setName('deploy')
46+
->setDescription('Deploys a Symfony application to one or more remote servers.')
47+
->setHelp('...')
48+
->addArgument('stage', InputArgument::OPTIONAL, 'The stage to deploy to ("production", "staging", etc.)', 'prod')
49+
->addOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Load configuration from the given file path')
50+
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Shows the commands to perform the deployment without actually executing them')
51+
;
52+
}
53+
54+
protected function initialize(InputInterface $input, OutputInterface $output)
55+
{
56+
$customConfigPath = $input->getOption('configuration');
57+
if (null !== $customConfigPath && !is_readable($customConfigPath)) {
58+
throw new \RuntimeException(sprintf("The given configuration file ('%s') does not exist or it's not readable.", $customConfigPath));
59+
}
60+
61+
if (null !== $customConfigPath && is_readable($customConfigPath)) {
62+
return $this->configFilePath = $customConfigPath;
63+
}
64+
65+
$defaultConfigPath = $this->getDefaultConfigPath($input->getArgument('stage'));
66+
if (is_readable($defaultConfigPath)) {
67+
return $this->configFilePath = $defaultConfigPath;
68+
}
69+
70+
$this->createDefaultConfigFile($input, $output, $defaultConfigPath, $input->getArgument('stage'));
71+
}
72+
73+
protected function execute(InputInterface $input, OutputInterface $output)
74+
{
75+
$logFilePath = sprintf('%s/deploy_%s.log', $this->logDir, $input->getArgument('stage'));
76+
$context = new Context($input, $output, $this->projectDir, $logFilePath, true === $input->getOption('dry-run'), $output->isVerbose());
77+
78+
$deployer = include $this->configFilePath;
79+
$deployer->initialize($context);
80+
$deployer->doDeploy();
81+
}
82+
83+
private function getDefaultConfigPath(string $stageName) : string
84+
{
85+
$symfonyVersion = Kernel::MAJOR_VERSION;
86+
$defaultConfigPaths = [
87+
2 => sprintf('%s/app/config/deploy_%s.php', $this->projectDir, $stageName),
88+
3 => sprintf('%s/app/config/deploy_%s.php', $this->projectDir, $stageName),
89+
4 => sprintf('%s/etc/%s/deploy.php', $this->projectDir, $stageName),
90+
];
91+
92+
if (!isset($defaultConfigPaths[$symfonyVersion])) {
93+
throw new SymfonyVersionException($symfonyVersion);
94+
}
95+
96+
return $defaultConfigPaths[$symfonyVersion];
97+
}
98+
99+
private function createDefaultConfigFile(InputInterface $input, OutputInterface $output, string $defaultConfigPath, string $stageName) : void
100+
{
101+
$helper = $this->getHelper('question');
102+
$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);
103+
104+
if (!$helper->ask($input, $output, $question)) {
105+
$output->writeln(sprintf('<fg=green>OK</>, but before running this command again, create this config file: %s', $defaultConfigPath));
106+
} else {
107+
(new Filesystem())->copy($this->fileLocator->locate('@EasyDeployBundle/Resources/skeleton/deploy.php.dist'), $defaultConfigPath);
108+
$output->writeln(sprintf('<fg=green>OK</>, now edit the "%s" config file and run this command again.', $defaultConfigPath));
109+
}
110+
111+
exit(0);
112+
}
113+
}

src/Command/RollbackCommand.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the EasyDeploy project.
5+
*
6+
* (c) Javier Eguiluz <javier.eguiluz@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace EasyCorp\Bundle\EasyDeployBundle\Command;
13+
14+
use EasyCorp\Bundle\EasyDeployBundle\Context;
15+
use EasyCorp\Bundle\EasyDeployBundle\Exception\SymfonyVersionException;
16+
use Symfony\Component\Console\Command\Command;
17+
use Symfony\Component\Console\Input\InputArgument;
18+
use Symfony\Component\Console\Input\InputInterface;
19+
use Symfony\Component\Console\Input\InputOption;
20+
use Symfony\Component\Console\Output\OutputInterface;
21+
use Symfony\Component\HttpKernel\Kernel;
22+
23+
class RollbackCommand extends Command
24+
{
25+
private $projectDir;
26+
private $logDir;
27+
private $configFilePath;
28+
29+
public function __construct(string $projectDir, string $logDir)
30+
{
31+
$this->projectDir = $projectDir;
32+
$this->logDir = $logDir;
33+
34+
parent::__construct();
35+
}
36+
37+
protected function configure()
38+
{
39+
$this
40+
->setName('rollback')
41+
->setDescription('Deploys a Symfony application to one or more remote servers.')
42+
->setHelp('...')
43+
->addArgument('stage', InputArgument::OPTIONAL, 'The stage to roll back ("production", "staging", etc.)', 'prod')
44+
->addOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Load configuration from the given file path')
45+
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Shows the commands to perform the roll back without actually executing them')
46+
;
47+
}
48+
49+
protected function initialize(InputInterface $input, OutputInterface $output)
50+
{
51+
$customConfigPath = $input->getOption('configuration');
52+
if (null !== $customConfigPath && !is_readable($customConfigPath)) {
53+
throw new \RuntimeException(sprintf("The given configuration file ('%s') does not exist or it's not readable.", $customConfigPath));
54+
}
55+
56+
if (null !== $customConfigPath && is_readable($customConfigPath)) {
57+
return $this->configFilePath = $customConfigPath;
58+
}
59+
60+
$defaultConfigPath = $this->getDefaultConfigPath($input->getArgument('stage'));
61+
if (is_readable($defaultConfigPath)) {
62+
return $this->configFilePath = $defaultConfigPath;
63+
}
64+
65+
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));
66+
}
67+
68+
protected function execute(InputInterface $input, OutputInterface $output)
69+
{
70+
$logFilePath = sprintf('%s/deploy_%s.log', $this->logDir, $input->getArgument('stage'));
71+
$context = new Context($input, $output, $this->projectDir, $logFilePath, true === $input->getOption('dry-run'), $output->isVerbose());
72+
73+
$deployer = include $this->configFilePath;
74+
$deployer->initialize($context);
75+
$deployer->doRollback();
76+
}
77+
78+
private function getDefaultConfigPath(string $stageName) : string
79+
{
80+
$symfonyVersion = Kernel::MAJOR_VERSION;
81+
$defaultConfigPaths = [
82+
2 => sprintf('%s/app/config/deploy_%s.php', $this->projectDir, $stageName),
83+
3 => sprintf('%s/app/config/deploy_%s.php', $this->projectDir, $stageName),
84+
4 => sprintf('%s/etc/%s/deploy.php', $this->projectDir, $stageName),
85+
];
86+
87+
if (!isset($defaultConfigPaths[$symfonyVersion])) {
88+
throw new SymfonyVersionException($symfonyVersion);
89+
}
90+
91+
return $defaultConfigPaths[$symfonyVersion];
92+
}
93+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the EasyDeploy project.
5+
*
6+
* (c) Javier Eguiluz <javier.eguiluz@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace EasyCorp\Bundle\EasyDeployBundle\Configuration;
13+
14+
use EasyCorp\Bundle\EasyDeployBundle\Exception\InvalidConfigurationException;
15+
use EasyCorp\Bundle\EasyDeployBundle\Server\Property;
16+
use EasyCorp\Bundle\EasyDeployBundle\Server\Server;
17+
use EasyCorp\Bundle\EasyDeployBundle\Server\ServerRepository;
18+
19+
/**
20+
* It implements the "Builder" pattern to define the configuration of the deployer.
21+
* This is the base builder extended by the specific builder used by each deployer.
22+
*/
23+
abstract class AbstractConfiguration
24+
{
25+
private const RESERVED_SERVER_PROPERTIES = [Property::use_ssh_agent_forwarding];
26+
protected $servers;
27+
protected $useSshAgentForwarding = true;
28+
29+
public function __construct()
30+
{
31+
$this->servers = new ServerRepository();
32+
}
33+
34+
public function server(string $sshDsn, array $roles = [Server::ROLE_APP], array $properties = [])
35+
{
36+
$reservedProperties = array_merge(self::RESERVED_SERVER_PROPERTIES, $this->getReservedServerProperties());
37+
$reservedPropertiesUsed = array_intersect($reservedProperties, array_keys($properties));
38+
if (!empty($reservedPropertiesUsed)) {
39+
throw new InvalidConfigurationException(sprintf('These properties set for the "%s" server are reserved: %s. Use different property names.', $sshDsn, implode(', ', $reservedPropertiesUsed)));
40+
}
41+
42+
$this->servers->add(new Server($sshDsn, $roles, $properties));
43+
}
44+
45+
public function useSshAgentForwarding(bool $useIt)
46+
{
47+
$this->useSshAgentForwarding = $useIt;
48+
}
49+
50+
abstract protected function getReservedServerProperties() : array;
51+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the EasyDeploy project.
5+
*
6+
* (c) Javier Eguiluz <javier.eguiluz@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace EasyCorp\Bundle\EasyDeployBundle\Configuration;
13+
14+
use EasyCorp\Bundle\EasyDeployBundle\Helper\Str;
15+
use Symfony\Component\HttpFoundation\ParameterBag;
16+
17+
/**
18+
* It implements the "Adapter" pattern to allow working with the configuration
19+
* in a consistent manner, even if the configuration of each deployer is
20+
* completely different and defined using incompatible objects.
21+
*/
22+
final class ConfigurationAdapter
23+
{
24+
private $config;
25+
/** @var ParameterBag */
26+
private $options;
27+
28+
public function __construct(AbstractConfiguration $config)
29+
{
30+
$this->config = $config;
31+
}
32+
33+
public function __toString() : string
34+
{
35+
return Str::formatAsTable($this->getOptions()->all());
36+
}
37+
38+
public function get(string $optionName)
39+
{
40+
if (!$this->getOptions()->has($optionName)) {
41+
throw new \InvalidArgumentException(sprintf('The "%s" option is not defined.', $optionName));
42+
}
43+
44+
return $this->getOptions()->get($optionName);
45+
}
46+
47+
private function getOptions() : ParameterBag
48+
{
49+
if (null !== $this->options) {
50+
return $this->options;
51+
}
52+
53+
// it's not the most beautiful code possible, but making the properties
54+
// private and the methods public allows to configure the deployment using
55+
// a config builder and the IDE autocompletion. Here we need to access
56+
// those private properties and their values
57+
$options = new ParameterBag();
58+
$r = new \ReflectionObject($this->config);
59+
foreach ($r->getProperties() as $property) {
60+
try {
61+
$property->setAccessible(true);
62+
$options->set($property->getName(), $property->getValue($this->config));
63+
} catch (\ReflectionException $e) {
64+
// ignore this error
65+
}
66+
}
67+
68+
return $this->options = $options;
69+
}
70+
}

0 commit comments

Comments
 (0)