Completed
Branch master (d1a89c)
by Andrés
05:54
created

DeployCommand::getException()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
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 56
    protected function configure()
43
    {
44
        $this
45 56
            ->setName('deploy')
46 56
            ->setDescription('Deploy code to hosts')
47 56
            ->addArgument('environment', InputArgument::REQUIRED, 'Name of the environment to deploy to')
48 56
            ->addOption('branch', null, InputOption::VALUE_REQUIRED, 'Force to switch to a branch other than the one defined', false)
49
        ;
50 56
    }
51
52
    /**
53
     * Execute the Command
54
     *
55
     * @param InputInterface $input
56
     * @param OutputInterface $output
57
     * @return integer
58
     */
59 37
    protected function execute(InputInterface $input, OutputInterface $output)
60
    {
61 37
        $this->requireConfig();
62
63 36
        $output->writeln('Starting <fg=blue>Magallanes</>');
64 36
        $output->writeln('');
65
66
        try {
67 36
            $this->runtime->setEnvironment($input->getArgument('environment'));
0 ignored issues
show
Bug introduced by
It seems like $input->getArgument('environment') targeting Symfony\Component\Consol...nterface::getArgument() can also be of type array<integer,string> or null; however, Mage\Runtime\Runtime::setEnvironment() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
68
69 36
            $strategy = $this->runtime->guessStrategy();
70 36
            $this->taskFactory = new TaskFactory($this->runtime);
71
72 33
            $output->writeln(sprintf('    Environment: <fg=green>%s</>', $this->runtime->getEnvironment()));
73 33
            $this->log(sprintf('Environment: %s', $this->runtime->getEnvironment()));
74
75 33
            if ($this->runtime->getEnvOption('releases', false)) {
76 11
                $this->runtime->generateReleaseId();
77 11
                $output->writeln(sprintf('    Release ID: <fg=green>%s</>', $this->runtime->getReleaseId()));
78 11
                $this->log(sprintf('Release ID: %s', $this->runtime->getReleaseId()));
79
            }
80
81 33 View Code Duplication
            if ($this->runtime->getConfigOption('log_file', false)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
82 32
                $output->writeln(sprintf('    Logfile: <fg=green>%s</>', $this->runtime->getConfigOption('log_file')));
83
            }
84
85 33
            $output->writeln(sprintf('    Strategy: <fg=green>%s</>', $strategy->getName()));
86
87 33
            if ($input->getOption('branch') !== false) {
88 1
                $this->runtime->setEnvOption('branch', $input->getOption('branch'));
89
            }
90
91 33
            if ($this->runtime->getEnvOption('branch', false)) {
92 26
                $output->writeln(sprintf('    Branch: <fg=green>%s</>', $this->runtime->getEnvOption('branch')));
93
            }
94
95 33
            $output->writeln('');
96 33
            $this->runDeployment($output, $strategy);
97 19
        } 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 36
        $output->writeln('Finished <fg=blue>Magallanes</>');
105
106 36
        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 34
    protected function runDeployment(OutputInterface $output, StrategyInterface $strategy)
117
    {
118
        // Run "Pre Deploy" Tasks
119 34
        $this->runtime->setStage(Runtime::PRE_DEPLOY);
120 34
        if (!$this->runTasks($output, $strategy->getPreDeployTasks())) {
121 4
            throw $this->getException();
122
        }
123
124
        // Run "On Deploy" Tasks
125 29
        $this->runtime->setStage(Runtime::ON_DEPLOY);
126 29
        $this->runOnHosts($output, $strategy->getOnDeployTasks());
127
128
        // Run "On Release" Tasks
129 22
        $this->runtime->setStage(Runtime::ON_RELEASE);
130 22
        $this->runOnHosts($output, $strategy->getOnReleaseTasks());
131
132
        // Run "Post Release" Tasks
133 22
        $this->runtime->setStage(Runtime::POST_RELEASE);
134 22
        $this->runOnHosts($output, $strategy->getPostReleaseTasks());
135
136
        // Run "Post Deploy" Tasks
137 21
        $this->runtime->setStage(Runtime::POST_DEPLOY);
138 21
        if (!$this->runTasks($output, $strategy->getPostDeployTasks())) {
139 3
            throw $this->getException();
140
        }
141 18
    }
142
143 29
    protected function runOnHosts(OutputInterface $output, $tasks)
144
    {
145 29
        $hosts = $this->runtime->getEnvOption('hosts');
146 29
        if (!is_array($hosts) && !$hosts instanceof \Countable) {
147 1
            $hosts = [];
148
        }
149 29
        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 28
        foreach ($hosts as $host) {
0 ignored issues
show
Bug introduced by
The expression $hosts of type array|object<Countable> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
156 28
            $this->runtime->setWorkingHost($host);
157 28
            if (!$this->runTasks($output, $tasks)) {
158 8
                $this->runtime->setWorkingHost(null);
159 8
                throw $this->getException();
160
            }
161 21
            $this->runtime->setWorkingHost(null);
162
        }
163 21
    }
164
165
    /**
166
     * Runs all the tasks
167
     *
168
     * @param OutputInterface $output
169
     * @param $tasks
170
     * @return bool
171
     * @throws RuntimeException
172
     */
173 34
    protected function runTasks(OutputInterface $output, $tasks)
174
    {
175 34
        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 34
        if ($this->runtime->getHostName() !== null) {
182 28
            $output->writeln(sprintf('    Starting <fg=black;options=bold>%s</> tasks on host <fg=black;options=bold>%s</>:', $this->getStageName(), $this->runtime->getHostName()));
183
        } else {
184 32
            $output->writeln(sprintf('    Starting <fg=black;options=bold>%s</> tasks:', $this->getStageName()));
185
        }
186
187 34
        $totalTasks = count($tasks);
188 34
        $succeededTasks = 0;
189
190 34
        foreach ($tasks as $taskName) {
191
            /** @var AbstractTask $task */
192 34
            $task = $this->taskFactory->get($taskName);
193 33
            $output->write(sprintf('        Running <fg=magenta>%s</> ... ', $task->getDescription()));
194 33
            $this->log(sprintf('Running task %s (%s)', $task->getDescription(), $task->getName()));
195
196 33
            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
            } else {
201
                try {
202 33
                    if ($task->execute()) {
203 31
                        $succeededTasks++;
204 31
                        $output->writeln('<fg=green>OK</>');
205 31
                        $this->log(sprintf('Task %s (%s) finished with OK', $task->getDescription(), $task->getName()));
206
                    } else {
207 9
                        $output->writeln('<fg=red>FAIL</>');
208 9
                        $this->statusCode = 180;
209 33
                        $this->log(sprintf('Task %s (%s) finished with FAIL', $task->getDescription(), $task->getName()));
210
                    }
211 7
                } 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 6
                } 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 33
            if ($this->statusCode !== 0) {
223 15
                break;
224
            }
225
        }
226
227 33
        $alertColor = 'red';
228 33
        if ($succeededTasks == $totalTasks) {
229 29
            $alertColor = 'green';
230
        }
231
232 33
        $output->writeln(sprintf('    Finished <fg=%s>%d/%d</> tasks for <fg=black;options=bold>%s</>.', $alertColor, $succeededTasks, $totalTasks, $this->getStageName()));
233 33
        $output->writeln('');
234
235 33
        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