DeployCommand::getException()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 10
1
<?php
2
3
/*
4
 * This file is part of the Magallanes package.
5
 *
6
 * (c) Andrés Montañez <[email protected]>
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 Mage\Command\BuiltIn;
13
14
use Mage\Deploy\Strategy\StrategyInterface;
15
use Mage\Runtime\Exception\RuntimeException;
16
use Mage\Runtime\Runtime;
17
use Mage\Task\ExecuteOnRollbackInterface;
18
use Mage\Task\AbstractTask;
19
use Mage\Task\Exception\ErrorException;
20
use Mage\Task\Exception\SkipException;
21
use Mage\Task\TaskFactory;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Input\InputArgument;
24
use Symfony\Component\Console\Input\InputOption;
25
use Symfony\Component\Console\Output\OutputInterface;
26
use Mage\Command\AbstractCommand;
27
28
/**
29
 * The Deployment Command
30
 *
31
 * @author Andrés Montañez <[email protected]>
32
 */
33
class DeployCommand extends AbstractCommand
34
{
35
    protected TaskFactory $taskFactory;
36
37
    /**
38
     * Configure the Command
39
     */
40 63
    protected function configure(): void
41
    {
42
        $this
43 63
            ->setName('deploy')
44 63
            ->setDescription('Deploy code to hosts')
45 63
            ->addArgument('environment', InputArgument::REQUIRED, 'Name of the environment to deploy to.')
46 63
            ->addOption(
47
                'branch',
48
                null,
49
                InputOption::VALUE_REQUIRED,
50
                'Force to switch to a branch other than the one defined.',
51
                false
52
            )
53 63
            ->addOption(
54
                'tag',
55
                null,
56
                InputOption::VALUE_REQUIRED,
57
                'Deploys a specific tag.',
58
                false
59
            );
60
    }
61
62
    /**
63
     * Execute the Command
64
     */
65 44
    protected function execute(InputInterface $input, OutputInterface $output): int
66
    {
67 44
        $this->requireConfig();
68
69 43
        $output->writeln('Starting <fg=blue>Magallanes</>');
70 43
        $output->writeln('');
71
72
        try {
73 43
            $this->runtime->setEnvironment($input->getArgument('environment'));
74
75 43
            $strategy = $this->runtime->guessStrategy();
76 43
            $this->taskFactory = new TaskFactory($this->runtime);
77
78 40
            $output->writeln(sprintf('    Environment: <fg=green>%s</>', $this->runtime->getEnvironment()));
79 40
            $this->log(sprintf('Environment: %s', $this->runtime->getEnvironment()));
80
81 40
            if ($this->runtime->getEnvOption('releases', false)) {
82 18
                $this->runtime->generateReleaseId();
83 18
                $output->writeln(sprintf('    Release ID: <fg=green>%s</>', $this->runtime->getReleaseId()));
84 18
                $this->log(sprintf('Release ID: %s', $this->runtime->getReleaseId()));
85
            }
86
87 40
            if ($this->runtime->getConfigOption('log_file', false)) {
88 39
                $output->writeln(sprintf('    Logfile: <fg=green>%s</>', $this->runtime->getConfigOption('log_file')));
89
            }
90
91 40
            $output->writeln(sprintf('    Strategy: <fg=green>%s</>', $strategy->getName()));
92
93 40
            if (($input->getOption('branch') !== false) && ($input->getOption('tag') !== false)) {
94 1
                throw new RuntimeException('Branch and Tag options are mutually exclusive.');
95
            }
96
97 39
            if ($input->getOption('branch') !== false) {
98 1
                $this->runtime->setEnvOption('branch', $input->getOption('branch'));
99
            }
100
101 39
            if ($input->getOption('tag') !== false) {
102 1
                $this->runtime->setEnvOption('branch', false);
103 1
                $this->runtime->setEnvOption('tag', $input->getOption('tag'));
104 1
                $output->writeln(sprintf('    Tag: <fg=green>%s</>', $this->runtime->getEnvOption('tag')));
105
            }
106
107 39
            if ($this->runtime->getEnvOption('branch', false)) {
108 31
                $output->writeln(sprintf('    Branch: <fg=green>%s</>', $this->runtime->getEnvOption('branch')));
109
            }
110
111
112 39
            $output->writeln('');
113 39
            $this->runDeployment($output, $strategy);
114 21
        } catch (RuntimeException $exception) {
115 21
            $output->writeln('');
116 21
            $output->writeln(sprintf('<error>%s</error>', $exception->getMessage()));
117 21
            $output->writeln('');
118 21
            $this->statusCode = 7;
119
        }
120
121 43
        $output->writeln('Finished <fg=blue>Magallanes</>');
122
123 43
        return intval($this->statusCode);
124
    }
125
126
    /**
127
     * Run the Deployment Process
128
     *
129
     * @throws RuntimeException
130
     */
131 40
    protected function runDeployment(OutputInterface $output, StrategyInterface $strategy): void
132
    {
133
        // Run "Pre Deploy" Tasks
134 40
        $this->runtime->setStage(Runtime::PRE_DEPLOY);
135 40
        if (!$this->runTasks($output, $strategy->getPreDeployTasks())) {
136 4
            throw $this->getException();
137
        }
138
139
        // Run "On Deploy" Tasks
140 35
        $this->runtime->setStage(Runtime::ON_DEPLOY);
141 35
        $this->runOnHosts($output, $strategy->getOnDeployTasks());
142
143
        // Run "On Release" Tasks
144 28
        $this->runtime->setStage(Runtime::ON_RELEASE);
145 28
        $this->runOnHosts($output, $strategy->getOnReleaseTasks());
146
147
        // Run "Post Release" Tasks
148 28
        $this->runtime->setStage(Runtime::POST_RELEASE);
149 28
        $this->runOnHosts($output, $strategy->getPostReleaseTasks());
150
151
        // Run "Post Deploy" Tasks
152 26
        $this->runtime->setStage(Runtime::POST_DEPLOY);
153 26
        if (!$this->runTasks($output, $strategy->getPostDeployTasks())) {
154 3
            throw $this->getException();
155
        }
156
    }
157
158
    /**
159
     * @param string[] $tasks
160
     */
161 35
    protected function runOnHosts(OutputInterface $output, array $tasks): void
162
    {
163 35
        $hosts = $this->runtime->getEnvOption('hosts');
164 35
        if (!is_array($hosts) && !$hosts instanceof \Countable) {
165 1
            $hosts = [];
166
        }
167
168 35
        if (count($hosts) === 0) {
169 1
            $output->writeln(sprintf('    No hosts defined, skipping %s tasks', $this->getStageName()));
170 1
            $output->writeln('');
171 1
            return;
172
        }
173
174 34
        foreach ($hosts as $host) {
175 34
            $this->runtime->setWorkingHost($host);
176 34
            if (!$this->runTasks($output, $tasks)) {
177 9
                $this->runtime->setWorkingHost(null);
178 9
                throw $this->getException();
179
            }
180 27
            $this->runtime->setWorkingHost(null);
181
        }
182
    }
183
184
    /**
185
     * Runs all the tasks
186
     *
187
     * @param string[] $tasks
188
     * @throws RuntimeException
189
     */
190 40
    protected function runTasks(OutputInterface $output, array $tasks): bool
191
    {
192 40
        if (count($tasks) == 0) {
193 15
            $output->writeln(
194 15
                sprintf('    No tasks defined for <fg=black;options=bold>%s</> stage', $this->getStageName())
195
            );
196 15
            $output->writeln('');
197 15
            return true;
198
        }
199
200 40
        if ($this->runtime->getHostName() !== null) {
201 34
            $output->writeln(
202 34
                sprintf(
203
                    '    Starting <fg=black;options=bold>%s</> tasks on host <fg=black;options=bold>%s</>:',
204 34
                    $this->getStageName(),
205 34
                    $this->runtime->getHostName()
206
                )
207
            );
208
        } else {
209 38
            $output->writeln(sprintf('    Starting <fg=black;options=bold>%s</> tasks:', $this->getStageName()));
210
        }
211
212 40
        $totalTasks = count($tasks);
213 40
        $succeededTasks = 0;
214
215 40
        foreach ($tasks as $taskName) {
216 40
            $task = $this->taskFactory->get($taskName);
217 39
            $output->write(sprintf('        Running <fg=magenta>%s</> ... ', $task->getDescription()));
218 39
            $this->log(sprintf('Running task %s (%s)', $task->getDescription(), $task->getName()));
219
220 39
            if ($this->runtime->inRollback() && !$task instanceof ExecuteOnRollbackInterface) {
221 1
                $succeededTasks++;
222 1
                $output->writeln('<fg=yellow>SKIPPED</>');
223 1
                $this->log(
224 1
                    sprintf(
225
                        'Task %s (%s) finished with SKIPPED, it was in a Rollback',
226 1
                        $task->getDescription(),
227 1
                        $task->getName()
228
                    )
229
                );
230
            } else {
231
                try {
232 39
                    if ($task->execute()) {
233 37
                        $succeededTasks++;
234 37
                        $output->writeln('<fg=green>OK</>');
235 37
                        $this->log(
236 37
                            sprintf('Task %s (%s) finished with OK', $task->getDescription(), $task->getName())
237
                        );
238
                    } else {
239 10
                        $output->writeln('<fg=red>FAIL</>');
240 10
                        $this->statusCode = 180;
241 10
                        $this->log(
242 39
                            sprintf('Task %s (%s) finished with FAIL', $task->getDescription(), $task->getName())
243
                        );
244
                    }
245 8
                } catch (SkipException $exception) {
246 2
                    $succeededTasks++;
247 2
                    $output->writeln('<fg=yellow>SKIPPED</>');
248 2
                    $this->log(
249 2
                        sprintf(
250
                            'Task %s (%s) finished with SKIPPED, thrown SkipException',
251 2
                            $task->getDescription(),
252 2
                            $task->getName()
253
                        )
254
                    );
255 6
                } catch (ErrorException $exception) {
256 6
                    $output->writeln(sprintf('<fg=red>ERROR</> [%s]', $exception->getTrimmedMessage()));
257 6
                    $this->log(
258 6
                        sprintf(
259
                            'Task %s (%s) finished with FAIL, with Error "%s"',
260 6
                            $task->getDescription(),
261 6
                            $task->getName(),
262 6
                            $exception->getMessage()
263
                        )
264
                    );
265 6
                    $this->statusCode = 190;
266
                }
267
            }
268
269 39
            if ($this->statusCode !== 0) {
270 16
                break;
271
            }
272
        }
273
274 39
        $alertColor = 'red';
275 39
        if ($succeededTasks == $totalTasks) {
276 35
            $alertColor = 'green';
277
        }
278
279 39
        $output->writeln(
280 39
            sprintf(
281
                '    Finished <fg=%s>%d/%d</> tasks for <fg=black;options=bold>%s</>.',
282
                $alertColor,
283
                $succeededTasks,
284
                $totalTasks,
285 39
                $this->getStageName()
286
            )
287
        );
288 39
        $output->writeln('');
289
290 39
        return ($succeededTasks == $totalTasks);
291
    }
292
293
    /**
294
     * Exception for halting the the current process
295
     */
296 16
    protected function getException(): RuntimeException
297
    {
298 16
        return new RuntimeException(
299 16
            sprintf('Stage "%s" did not finished successfully, halting command.', $this->getStageName()),
300
            50
301
        );
302
    }
303
}
304