1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* This file is part of the Magallanes package. |
4
|
|
|
* |
5
|
|
|
* (c) Andrés Montañez <[email protected]> |
6
|
|
|
* |
7
|
|
|
* For the full copyright and license information, please view the LICENSE |
8
|
|
|
* file that was distributed with this source code. |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
namespace Mage\Command\BuiltIn; |
12
|
|
|
|
13
|
|
|
use Mage\Deploy\Strategy\StrategyInterface; |
14
|
|
|
use Mage\Runtime\Exception\RuntimeException; |
15
|
|
|
use Mage\Runtime\Runtime; |
16
|
|
|
use Mage\Task\ExecuteOnRollbackInterface; |
17
|
|
|
use Mage\Task\AbstractTask; |
18
|
|
|
use Mage\Task\Exception\ErrorException; |
19
|
|
|
use Mage\Task\Exception\SkipException; |
20
|
|
|
use Mage\Task\TaskFactory; |
21
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
22
|
|
|
use Symfony\Component\Console\Input\InputArgument; |
23
|
|
|
use Symfony\Component\Console\Input\InputOption; |
24
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
25
|
|
|
use Mage\Command\AbstractCommand; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* The Deployment Command |
29
|
|
|
* |
30
|
|
|
* @author Andrés Montañez <[email protected]> |
31
|
|
|
*/ |
32
|
|
|
class DeployCommand extends AbstractCommand |
33
|
|
|
{ |
34
|
|
|
/** |
35
|
|
|
* @var TaskFactory |
36
|
|
|
*/ |
37
|
|
|
protected $taskFactory; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Configure the Command |
41
|
|
|
*/ |
42
|
55 |
|
protected function configure() |
43
|
|
|
{ |
44
|
55 |
|
$this |
45
|
55 |
|
->setName('deploy') |
46
|
55 |
|
->setDescription('Deploy code to hosts') |
47
|
55 |
|
->addArgument('environment', InputArgument::REQUIRED, 'Name of the environment to deploy to') |
48
|
55 |
|
->addOption('branch', null, InputOption::VALUE_REQUIRED, 'Force to switch to a branch other than the one defined', false) |
49
|
|
|
; |
50
|
55 |
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Execute the Command |
54
|
|
|
* |
55
|
|
|
* @param InputInterface $input |
56
|
|
|
* @param OutputInterface $output |
57
|
|
|
* @return integer |
58
|
|
|
*/ |
59
|
36 |
|
protected function execute(InputInterface $input, OutputInterface $output) |
60
|
|
|
{ |
61
|
36 |
|
$this->requireConfig(); |
62
|
|
|
|
63
|
35 |
|
$output->writeln('Starting <fg=blue>Magallanes</>'); |
64
|
35 |
|
$output->writeln(''); |
65
|
|
|
|
66
|
|
|
try { |
67
|
35 |
|
$this->runtime->setEnvironment($input->getArgument('environment')); |
68
|
|
|
|
69
|
35 |
|
$strategy = $this->runtime->guessStrategy(); |
70
|
35 |
|
$this->taskFactory = new TaskFactory($this->runtime); |
71
|
|
|
|
72
|
32 |
|
$output->writeln(sprintf(' Environment: <fg=green>%s</>', $this->runtime->getEnvironment())); |
73
|
32 |
|
$this->log(sprintf('Environment: %s', $this->runtime->getEnvironment())); |
74
|
|
|
|
75
|
32 |
|
if ($this->runtime->getEnvOption('releases', false)) { |
76
|
10 |
|
$this->runtime->generateReleaseId(); |
77
|
10 |
|
$output->writeln(sprintf(' Release ID: <fg=green>%s</>', $this->runtime->getReleaseId())); |
78
|
10 |
|
$this->log(sprintf('Release ID: %s', $this->runtime->getReleaseId())); |
79
|
10 |
|
} |
80
|
|
|
|
81
|
32 |
View Code Duplication |
if ($this->runtime->getConfigOption('log_file', false)) { |
|
|
|
|
82
|
31 |
|
$output->writeln(sprintf(' Logfile: <fg=green>%s</>', $this->runtime->getConfigOption('log_file'))); |
83
|
31 |
|
} |
84
|
|
|
|
85
|
32 |
|
$output->writeln(sprintf(' Strategy: <fg=green>%s</>', $strategy->getName())); |
86
|
|
|
|
87
|
32 |
|
if ($input->getOption('branch') !== false) { |
88
|
1 |
|
$this->runtime->setEnvOption('branch', $input->getOption('branch')); |
89
|
1 |
|
} |
90
|
|
|
|
91
|
32 |
|
if ($this->runtime->getEnvOption('branch', false)) { |
92
|
25 |
|
$output->writeln(sprintf(' Branch: <fg=green>%s</>', $this->runtime->getEnvOption('branch'))); |
93
|
26 |
|
} |
94
|
|
|
|
95
|
32 |
|
$output->writeln(''); |
96
|
32 |
|
$this->runDeployment($output, $strategy); |
97
|
35 |
|
} catch (RuntimeException $exception) { |
98
|
19 |
|
$output->writeln(''); |
99
|
19 |
|
$output->writeln(sprintf('<error>%s</error>', $exception->getMessage())); |
100
|
19 |
|
$output->writeln(''); |
101
|
19 |
|
$this->statusCode = 7; |
102
|
|
|
} |
103
|
|
|
|
104
|
35 |
|
$output->writeln('Finished <fg=blue>Magallanes</>'); |
105
|
|
|
|
106
|
35 |
|
return $this->statusCode; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Run the Deployment Process |
111
|
|
|
* |
112
|
|
|
* @param OutputInterface $output |
113
|
|
|
* @param StrategyInterface $strategy |
114
|
|
|
* @throws RuntimeException |
115
|
|
|
*/ |
116
|
33 |
|
protected function runDeployment(OutputInterface $output, StrategyInterface $strategy) |
117
|
|
|
{ |
118
|
|
|
// Run "Pre Deploy" Tasks |
119
|
33 |
|
$this->runtime->setStage(Runtime::PRE_DEPLOY); |
120
|
33 |
|
if (!$this->runTasks($output, $strategy->getPreDeployTasks())) { |
121
|
4 |
|
throw $this->getException(); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
// Run "On Deploy" Tasks |
125
|
28 |
|
$this->runtime->setStage(Runtime::ON_DEPLOY); |
126
|
28 |
|
$this->runOnHosts($output, $strategy->getOnDeployTasks()); |
127
|
|
|
|
128
|
|
|
// Run "On Release" Tasks |
129
|
21 |
|
$this->runtime->setStage(Runtime::ON_RELEASE); |
130
|
21 |
|
$this->runOnHosts($output, $strategy->getOnReleaseTasks()); |
131
|
|
|
|
132
|
|
|
// Run "Post Release" Tasks |
133
|
21 |
|
$this->runtime->setStage(Runtime::POST_RELEASE); |
134
|
21 |
|
$this->runOnHosts($output, $strategy->getPostReleaseTasks()); |
135
|
|
|
|
136
|
|
|
// Run "Post Deploy" Tasks |
137
|
20 |
|
$this->runtime->setStage(Runtime::POST_DEPLOY); |
138
|
20 |
|
if (!$this->runTasks($output, $strategy->getPostDeployTasks())) { |
139
|
3 |
|
throw $this->getException(); |
140
|
|
|
} |
141
|
17 |
|
} |
142
|
|
|
|
143
|
28 |
|
protected function runOnHosts(OutputInterface $output, $tasks) |
144
|
|
|
{ |
145
|
28 |
|
$hosts = $this->runtime->getEnvOption('hosts'); |
146
|
28 |
|
if (!is_array($hosts) && !$hosts instanceof \Countable) { |
147
|
1 |
|
$hosts = []; |
148
|
1 |
|
} |
149
|
28 |
|
if (count($hosts) == 0) { |
150
|
1 |
|
$output->writeln(sprintf(' No hosts defined, skipping %s tasks', $this->getStageName())); |
151
|
1 |
|
$output->writeln(''); |
152
|
1 |
|
return true; |
153
|
|
|
} |
154
|
|
|
|
155
|
27 |
|
foreach ($hosts as $host) { |
|
|
|
|
156
|
27 |
|
$this->runtime->setWorkingHost($host); |
157
|
27 |
|
if (!$this->runTasks($output, $tasks)) { |
158
|
8 |
|
$this->runtime->setWorkingHost(null); |
159
|
8 |
|
throw $this->getException(); |
160
|
|
|
} |
161
|
20 |
|
$this->runtime->setWorkingHost(null); |
162
|
20 |
|
} |
163
|
20 |
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Runs all the tasks |
167
|
|
|
* |
168
|
|
|
* @param OutputInterface $output |
169
|
|
|
* @param $tasks |
170
|
|
|
* @return bool |
171
|
|
|
* @throws RuntimeException |
172
|
|
|
*/ |
173
|
33 |
|
protected function runTasks(OutputInterface $output, $tasks) |
174
|
|
|
{ |
175
|
33 |
|
if (count($tasks) == 0) { |
176
|
15 |
|
$output->writeln(sprintf(' No tasks defined for <fg=black;options=bold>%s</> stage', $this->getStageName())); |
177
|
15 |
|
$output->writeln(''); |
178
|
15 |
|
return true; |
179
|
|
|
} |
180
|
|
|
|
181
|
33 |
|
if ($this->runtime->getHostName() !== null) { |
182
|
27 |
|
$output->writeln(sprintf(' Starting <fg=black;options=bold>%s</> tasks on host <fg=black;options=bold>%s</>:', $this->getStageName(), $this->runtime->getHostName())); |
183
|
27 |
|
} else { |
184
|
31 |
|
$output->writeln(sprintf(' Starting <fg=black;options=bold>%s</> tasks:', $this->getStageName())); |
185
|
|
|
} |
186
|
|
|
|
187
|
33 |
|
$totalTasks = count($tasks); |
188
|
33 |
|
$succeededTasks = 0; |
189
|
|
|
|
190
|
33 |
|
foreach ($tasks as $taskName) { |
191
|
|
|
/** @var AbstractTask $task */ |
192
|
33 |
|
$task = $this->taskFactory->get($taskName); |
193
|
32 |
|
$output->write(sprintf(' Running <fg=magenta>%s</> ... ', $task->getDescription())); |
194
|
32 |
|
$this->log(sprintf('Running task %s (%s)', $task->getDescription(), $task->getName())); |
195
|
|
|
|
196
|
32 |
|
if ($this->runtime->inRollback() && !$task instanceof ExecuteOnRollbackInterface) { |
197
|
1 |
|
$succeededTasks++; |
198
|
1 |
|
$output->writeln('<fg=yellow>SKIPPED</>'); |
199
|
1 |
|
$this->log(sprintf('Task %s (%s) finished with SKIPPED, it was in a Rollback', $task->getDescription(), $task->getName())); |
200
|
1 |
|
} else { |
201
|
|
|
try { |
202
|
32 |
|
if ($task->execute()) { |
203
|
30 |
|
$succeededTasks++; |
204
|
30 |
|
$output->writeln('<fg=green>OK</>'); |
205
|
30 |
|
$this->log(sprintf('Task %s (%s) finished with OK', $task->getDescription(), $task->getName())); |
206
|
30 |
|
} else { |
207
|
9 |
|
$output->writeln('<fg=red>FAIL</>'); |
208
|
9 |
|
$this->statusCode = 180; |
209
|
9 |
|
$this->log(sprintf('Task %s (%s) finished with FAIL', $task->getDescription(), $task->getName())); |
210
|
|
|
} |
211
|
32 |
|
} catch (SkipException $exception) { |
212
|
1 |
|
$succeededTasks++; |
213
|
1 |
|
$output->writeln('<fg=yellow>SKIPPED</>'); |
214
|
1 |
|
$this->log(sprintf('Task %s (%s) finished with SKIPPED, thrown SkipException', $task->getDescription(), $task->getName())); |
215
|
7 |
|
} catch (ErrorException $exception) { |
216
|
6 |
|
$output->writeln(sprintf('<fg=red>ERROR</> [%s]', $exception->getTrimmedMessage())); |
217
|
6 |
|
$this->log(sprintf('Task %s (%s) finished with FAIL, with Error "%s"', $task->getDescription(), $task->getName(), $exception->getMessage())); |
218
|
6 |
|
$this->statusCode = 190; |
219
|
|
|
} |
220
|
|
|
} |
221
|
|
|
|
222
|
32 |
|
if ($this->statusCode !== 0) { |
223
|
15 |
|
break; |
224
|
|
|
} |
225
|
32 |
|
} |
226
|
|
|
|
227
|
32 |
|
$alertColor = 'red'; |
228
|
32 |
|
if ($succeededTasks == $totalTasks) { |
229
|
28 |
|
$alertColor = 'green'; |
230
|
28 |
|
} |
231
|
|
|
|
232
|
32 |
|
$output->writeln(sprintf(' Finished <fg=%s>%d/%d</> tasks for <fg=black;options=bold>%s</>.', $alertColor, $succeededTasks, $totalTasks, $this->getStageName())); |
233
|
32 |
|
$output->writeln(''); |
234
|
|
|
|
235
|
32 |
|
return ($succeededTasks == $totalTasks); |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Exception for halting the the current process |
240
|
|
|
*/ |
241
|
15 |
|
protected function getException() |
242
|
|
|
{ |
243
|
15 |
|
return new RuntimeException(sprintf('Stage "%s" did not finished successfully, halting command.', $this->getStageName()), 50); |
244
|
|
|
} |
245
|
|
|
} |
246
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.