Passed
Push — master ( 27923f...23dae9 )
by Andrés
04:29
created

DeployCommand::runOnHosts()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 6

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 21
ccs 17
cts 17
cp 1
rs 8.7624
cc 6
eloc 14
nc 8
nop 2
crap 6
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)) {
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 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) {
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 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