GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( e51949...763526 )
by Anton
01:57
created

ParallelExecutor::run()   B

Complexity

Conditions 8
Paths 12

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
cc 8
nc 12
nop 2
dl 0
loc 31
rs 8.1795
c 0
b 0
f 0
ccs 0
cts 26
cp 0
crap 72
1
<?php declare(strict_types=1);
2
/* (c) Anton Medvedev <[email protected]>
3
 *
4
 * For the full copyright and license information, please view the LICENSE
5
 * file that was distributed with this source code.
6
 */
7
8
namespace Deployer\Executor;
9
10
use Deployer\Collection\PersistentCollection;
11
use Deployer\Configuration\Configuration;
12
use Deployer\Console\Application;
13
use Deployer\Deployer;
14
use Deployer\Exception\Exception;
15
use Deployer\Exception\GracefulShutdownException;
16
use Deployer\Host\Host;
17
use Deployer\Host\Localhost;
18
use Deployer\Host\Storage;
19
use Deployer\Component\Ssh\Client;
20
use Deployer\Task\Context;
21
use Deployer\Task\Task;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Output\OutputInterface;
24
use Symfony\Component\Process\Process;
25
26
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
27
28
function spinner($message = '')
29
{
30
    $frame = FRAMES[(int)(microtime(true) * 10) % count(FRAMES)];
31
    return "  $frame $message\r";
32
}
33
34
class ParallelExecutor
35
{
36
    private $input;
37
    private $output;
38
    private $messenger;
39
    private $console;
40
    private $client;
41
    private $config;
42
43
    public function __construct(
44
        InputInterface $input,
45
        OutputInterface $output,
46
        Messenger $messenger,
47
        Application $console,
48
        Client $client,
49
        Configuration $config
50
    )
51
    {
52
        $this->input = $input;
53
        $this->output = $output;
54
        $this->messenger = $messenger;
55
        $this->console = $console;
56
        $this->client = $client;
57
        $this->config = $config;
58
    }
59
60
    /**
61
     * @param Host[] $hosts
62
     */
63
    private function connect(array $hosts)
64
    {
65 View Code Duplication
        $callback = function (string $output) {
66
            $output = preg_replace('/\n$/', '', $output);
67
            if (strlen($output) !== 0) {
68
                $this->output->writeln($output);
69
            }
70
        };
71
72
        // Connect to each host sequentially, to prevent getting locked.
73
        foreach ($hosts as $host) {
74
            if ($host instanceof Localhost) {
75
                continue;
76
            }
77
            $process = $this->getProcess($host, new Task('connect'));
78
            $process->start();
79
80
            while ($process->isRunning()) {
81
                $this->gatherOutput([$process], $callback);
82
                $this->output->write(spinner(str_pad("connect {$host->tag()}", intval(getenv('COLUMNS')) - 1)));
83
                usleep(1000);
84
            }
85
        }
86
87
        // Clear spinner.
88
        $this->output->write(str_repeat(' ', intval(getenv('COLUMNS')) - 1) . "\r");
89
    }
90
91
    /**
92
     * @param Task[] $tasks
93
     * @param Host[] $hosts
94
     */
95
    public function run(array $tasks, array $hosts): int
96
    {
97
        $this->persistHosts($hosts);
98
        $this->connect($hosts);
99
100
        $localhost = new Localhost();
0 ignored issues
show
Unused Code introduced by Anton Medvedev
$localhost is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
101
        $limit = (int)$this->input->getOption('limit') ?: count($hosts);
102
103
        foreach ($tasks as $task) {
104
            $this->messenger->startTask($task);
105
106
            if ($limit === 1 || count($hosts) === 1) {
107
                foreach ($hosts as $host) {
108
                    $host->getConfig()->getCollection()->load();
0 ignored issues
show
Bug introduced by Anton Medvedev
It seems like you code against a specific sub-type and not the parent class Deployer\Collection\Collection as the method load() does only exist in the following sub-classes of Deployer\Collection\Collection: Deployer\Collection\PersistentCollection. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
109
                    $task->run(new Context($host, $this->input, $this->output));
110
                    $host->getConfig()->getCollection()->flush();
0 ignored issues
show
Bug introduced by Anton Medvedev
It seems like you code against a specific sub-type and not the parent class Deployer\Collection\Collection as the method flush() does only exist in the following sub-classes of Deployer\Collection\Collection: Deployer\Collection\PersistentCollection. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
111
                }
112
            } else {
113
                foreach (array_chunk($hosts, $limit) as $chunk) {
114
                    $exitCode = $this->runTask($chunk, $task);
115
                    if ($exitCode !== 0) {
116
                        return $exitCode;
117
                    }
118
                }
119
            }
120
121
            $this->messenger->endTask($task);
122
        }
123
124
        return 0;
125
    }
126
127
    private function runTask(array $hosts, Task $task): int
128
    {
129
        $processes = [];
130
        foreach ($hosts as $host) {
131
            if ($task->shouldBePerformed($host)) {
132
                $processes[] = $this->getProcess($host, $task);
133
                if ($task->isOnce()) {
134
                    $task->setHasRun();
135
                }
136
            }
137
        }
138
139 View Code Duplication
        $callback = function (string $output) {
140
            $output = preg_replace('/\n$/', '', $output);
141
            if (strlen($output) !== 0) {
142
                $this->output->writeln($output);
143
            }
144
        };
145
146
        $this->startProcesses($processes);
147
148
        while ($this->areRunning($processes)) {
149
            $this->gatherOutput($processes, $callback);
150
            $this->output->write(spinner());
151
            usleep(1000);
152
        }
153
154
        // Clear spinner.
155
        $this->output->write("    \r");
156
157
        $this->gatherOutput($processes, $callback);
158
159
        return $this->gatherExitCodes($processes);
160
    }
161
162
    protected function getProcess(Host $host, Task $task): Process
163
    {
164
        $dep = PHP_BINARY . ' ' . DEPLOYER_BIN;
165
        $configFile = $host->get('worker-config');
166
        $decorated = $this->output->isDecorated() ? '--decorated' : '';
167
        $command = "$dep worker $task {$host->alias()} $configFile {$this->input} $decorated";
168
169
        if ($this->output->isDebug()) {
170
            $this->output->writeln("[{$host->tag()}] $command");
171
        }
172
173
        return Process::fromShellCommandline($command);
174
    }
175
176
    /**
177
     * Start all of the processes.
178
     *
179
     * @param Process[] $processes
180
     * @return void
181
     */
182
    protected function startProcesses(array $processes)
183
    {
184
        foreach ($processes as $process) {
185
            $process->start();
186
        }
187
    }
188
189
    /**
190
     * Determine if any of the processes are running.
191
     *
192
     * @param Process[] $processes
193
     * @return bool
194
     */
195
    protected function areRunning(array $processes): bool
196
    {
197
        foreach ($processes as $process) {
198
            if ($process->isRunning()) {
199
                return true;
200
            }
201
        }
202
203
        return false;
204
    }
205
206
    /**
207
     * Gather the output from all of the processes.
208
     *
209
     * @param Process[] $processes
210
     * @param callable $callback
211
     * @return void
212
     */
213
    protected function gatherOutput(array $processes, callable $callback)
214
    {
215
        foreach ($processes as $process) {
216
            $output = $process->getIncrementalOutput();
217
            if (strlen($output) !== 0) {
218
                $callback($output);
219
            }
220
221
            $errorOutput = $process->getIncrementalErrorOutput();
222
            if (strlen($errorOutput) !== 0) {
223
                $callback($errorOutput);
224
            }
225
        }
226
    }
227
228
    /**
229
     * Gather the cumulative exit code for the processes.
230
     */
231
    protected function gatherExitCodes(array $processes): int
232
    {
233
        foreach ($processes as $process) {
234
            if ($process->getExitCode() > 0) {
235
                return $process->getExitCode();
236
            }
237
        }
238
239
        return 0;
240
    }
241
242
    /**
243
     * @param Host[] $hosts
244
     */
245
    private function persistHosts(array $hosts)
246
    {
247
        foreach ($hosts as $host) {
248
            Context::push(new Context($host, $this->input, $this->output));
249
250
            $values = $host->getConfig()->persist();
251
            $workerConfig = sys_get_temp_dir() . '/' . uniqid('deployer-') . '-' . $host->alias() . '.dep';
252
            $values['worker-config'] = $workerConfig;
253
254
            $persistentCollection = new PersistentCollection($workerConfig, $values);
255
            $persistentCollection->flush();
256
            $host->getConfig()->setCollection($persistentCollection);
257
258
            Context::pop();
259
        }
260
    }
261
}
262