Test Failed
Pull Request — master (#448)
by
unknown
05:18
created

DeployCommand::execute()   B

Complexity

Conditions 6
Paths 89

Size

Total Lines 49

Duplication

Lines 3
Ratio 6.12 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 3
loc 49
ccs 0
cts 37
cp 0
rs 8.4905
c 0
b 0
f 0
cc 6
nc 89
nop 2
crap 42
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
    protected function configure()
43
    {
44
        $this
45
            ->setName('deploy')
46
            ->setDescription('Deploy code to hosts')
47
            ->addArgument('environment', InputArgument::REQUIRED, 'Name of the environment to deploy to')
48
            ->addOption('branch', null, InputOption::VALUE_REQUIRED, 'Force to switch to a branch other than the one defined', false)
49
        ;
50
    }
51
52
    /**
53
     * Execute the Command
54
     *
55
     * @param InputInterface $input
56
     * @param OutputInterface $output
57
     * @return integer
58
     */
59
    protected function execute(InputInterface $input, OutputInterface $output)
60
    {
61
        $this->requireConfig();
62
63
        $output->writeln('Starting <fg=blue>Magallanes</>');
64
        $output->writeln('');
65
66
        try {
67
            $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
            $strategy = $this->runtime->guessStrategy();
70
            $this->taskFactory = new TaskFactory($this->runtime);
71
72
            $output->writeln(sprintf('    Environment: <fg=green>%s</>', $this->runtime->getEnvironment()));
73
            $this->log(sprintf('Environment: %s', $this->runtime->getEnvironment()));
74
75
            if ($this->runtime->getEnvOption('releases', false)) {
76
                $this->runtime->generateReleaseId();
77
                $output->writeln(sprintf('    Release ID: <fg=green>%s</>', $this->runtime->getReleaseId()));
78
                $this->log(sprintf('Release ID: %s', $this->runtime->getReleaseId()));
79
            }
80
81 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
                $output->writeln(sprintf('    Logfile: <fg=green>%s</>', $this->runtime->getConfigOption('log_file')));
83
            }
84
85
            $output->writeln(sprintf('    Strategy: <fg=green>%s</>', $strategy->getName()));
86
87
            if ($input->getOption('branch') !== false) {
88
                $this->runtime->setEnvOption('branch', $input->getOption('branch'));
89
            }
90
91
            if ($this->runtime->getEnvOption('branch', false)) {
92
                $output->writeln(sprintf('    Branch: <fg=green>%s</>', $this->runtime->getEnvOption('branch')));
93
            }
94
95
            $output->writeln('');
96
            $this->runDeployment($output, $strategy);
97
        } catch (RuntimeException $exception) {
98
            $output->writeln('');
99
            $output->writeln(sprintf('<error>%s</error>', $exception->getMessage()));
100
            $output->writeln('');
101
            $this->statusCode = 7;
102
        }
103
104
        $output->writeln('Finished <fg=blue>Magallanes</>');
105
106
        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
    protected function runDeployment(OutputInterface $output, StrategyInterface $strategy)
117
    {
118
        // Run "Pre Deploy" Tasks
119
        $this->runtime->setStage(Runtime::PRE_DEPLOY);
120
        if (!$this->runTasks($output, $strategy->getPreDeployTasks())) {
121
            throw $this->getException();
122
        }
123
124
        // Run "On Deploy" Tasks
125
        $this->runtime->setStage(Runtime::ON_DEPLOY);
126
        $this->runOnHosts($output, $strategy->getOnDeployTasks());
127
128
        // Run "On Release" Tasks
129
        $this->runtime->setStage(Runtime::ON_RELEASE);
130
        $this->runOnHosts($output, $strategy->getOnReleaseTasks());
131
132
        // Run "Post Release" Tasks
133
        $this->runtime->setStage(Runtime::POST_RELEASE);
134
        $this->runOnHosts($output, $strategy->getPostReleaseTasks());
135
136
        // Run "Post Deploy" Tasks
137
        $this->runtime->setStage(Runtime::POST_DEPLOY);
138
        if (!$this->runTasks($output, $strategy->getPostDeployTasks())) {
139
            throw $this->getException();
140
        }
141
    }
142
143
    protected function runOnHosts(OutputInterface $output, $tasks)
144
    {
145
        $hosts = $this->runtime->getEnvOption('hosts');
146
        if (!is_array($hosts) && !$hosts instanceof \Countable) {
147
            $hosts = [];
148
        }
149
        if (count($hosts) == 0) {
150
            $output->writeln(sprintf('    No hosts defined, skipping %s tasks', $this->getStageName()));
151
            $output->writeln('');
152
            return true;
153
        }
154
155
        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
            $this->runtime->setWorkingHost($host);
157
            if (!$this->runTasks($output, $tasks)) {
158
                $this->runtime->setWorkingHost(null);
159
                throw $this->getException();
160
            }
161
            $this->runtime->setWorkingHost(null);
162
        }
163
    }
164
165
    /**
166
     * Runs all the tasks
167
     *
168
     * @param OutputInterface $output
169
     * @param $tasks
170
     * @return bool
171
     * @throws RuntimeException
172
     */
173
    protected function runTasks(OutputInterface $output, $tasks)
174
    {
175
        if (count($tasks) == 0) {
176
            $output->writeln(sprintf('    No tasks defined for <fg=black;options=bold>%s</> stage', $this->getStageName()));
177
            $output->writeln('');
178
            return true;
179
        }
180
181
        if ($this->runtime->getHostName() !== null) {
182
            $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
            $output->writeln(sprintf('    Starting <fg=black;options=bold>%s</> tasks:', $this->getStageName()));
185
        }
186
187
        $totalTasks = count($tasks);
188
        $succeededTasks = 0;
189
190
        foreach ($tasks as $taskName) {
191
            /** @var AbstractTask $task */
192
            $task = $this->taskFactory->get($taskName);
193
            $output->write(sprintf('        Running <fg=magenta>%s</> ... ', $task->getDescription()));
194
            $this->log(sprintf('Running task %s (%s)', $task->getDescription(), $task->getName()));
195
196
            if ($this->runtime->inRollback() && !$task instanceof ExecuteOnRollbackInterface) {
197
                $succeededTasks++;
198
                $output->writeln('<fg=yellow>SKIPPED</>');
199
                $this->log(sprintf('Task %s (%s) finished with SKIPPED, it was in a Rollback', $task->getDescription(), $task->getName()));
200
            } else {
201
                try {
202
                    if ($task->execute()) {
203
                        $succeededTasks++;
204
                        $output->writeln('<fg=green>OK</>');
205
                        $this->log(sprintf('Task %s (%s) finished with OK', $task->getDescription(), $task->getName()));
206
                    } else {
207
                        $output->writeln('<fg=red>FAIL</>');
208
                        $this->statusCode = 180;
209
                        $this->log(sprintf('Task %s (%s) finished with FAIL', $task->getDescription(), $task->getName()));
210
                    }
211
                } catch (SkipException $exception) {
212
                    $succeededTasks++;
213
                    $output->writeln('<fg=yellow>SKIPPED</>');
214
                    $this->log(sprintf('Task %s (%s) finished with SKIPPED, thrown SkipException', $task->getDescription(), $task->getName()));
215
                } catch (ErrorException $exception) {
216
                    $output->writeln(sprintf('<fg=red>ERROR</> [%s]', $exception->getTrimmedMessage()));
217
                    $this->log(sprintf('Task %s (%s) finished with FAIL, with Error "%s"', $task->getDescription(), $task->getName(), $exception->getMessage()));
218
                    $this->statusCode = 190;
219
                }
220
            }
221
222
            if ($this->statusCode !== 0) {
223
                break;
224
            }
225
        }
226
227
        $alertColor = 'red';
228
        if ($succeededTasks == $totalTasks) {
229
            $alertColor = 'green';
230
        }
231
232
        $output->writeln(sprintf('    Finished <fg=%s>%d/%d</> tasks for <fg=black;options=bold>%s</>.', $alertColor, $succeededTasks, $totalTasks, $this->getStageName()));
233
        $output->writeln('');
234
235
        return ($succeededTasks == $totalTasks);
236
    }
237
238
    /**
239
     * Exception for halting the the current process
240
     */
241
    protected function getException()
242
    {
243
        return new RuntimeException(sprintf('Stage "%s" did not finished successfully, halting command.', $this->getStageName()), 50);
244
    }
245
}
246