Client::run()   B
last analyzed

Complexity

Conditions 7
Paths 18

Size

Total Lines 66

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
nc 18
nop 3
dl 0
loc 66
ccs 0
cts 50
cp 0
crap 56
rs 7.8084
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
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\Ssh;
9
10
use Deployer\Deployer;
11
use Deployer\Exception\RuntimeException;
12
use Deployer\Host\Host;
13
use Deployer\Utility\ProcessOutputPrinter;
14
use Symfony\Component\Console\Output\OutputInterface;
15
use Symfony\Component\Process\Process;
16
17
class Client
18
{
19
    /**
20
     * @var OutputInterface
21
     */
22
    private $output;
23
24
    /**
25
     * @var ProcessOutputPrinter
26
     */
27
    private $pop;
28
29
    /**
30
     * @var bool
31
     */
32
    private $multiplexing;
33
34
    public function __construct(OutputInterface $output, ProcessOutputPrinter $pop, bool $multiplexing)
35
    {
36
        $this->output = $output;
37
        $this->pop = $pop;
38
        $this->multiplexing = $multiplexing;
39
    }
40
41
    /**
42
     * @param Host $host
43
     * @param string $command
44
     * @param array $config
45
     * @return string
46
     * @throws RuntimeException
47
     */
48
    public function run(Host $host, string $command, array $config = [])
49
    {
50
        $hostname = $host->getHostname();
51
        $defaults = [
52
            'timeout' => Deployer::getDefault('default_timeout', 300),
53
            'tty' => false,
54
        ];
55
        $config = array_merge($defaults, $config);
56
57
        $this->pop->command($hostname, $command);
58
59
        $sshArguments = $host->getSshArguments();
60
61
        $become = $host->has('become') ? 'sudo -H -u ' . $host->get('become') : '';
62
63
        // When tty need to be allocated, don't use multiplexing,
64
        // and pass command without bash allocation on remote host.
65
        if ($config['tty']) {
66
            $this->output->write(''); // Notify OutputWatcher
67
            $sshArguments = $sshArguments->withFlag('-tt');
68
            $command = escapeshellarg($command);
69
70
            $ssh = "ssh $sshArguments $host $command";
71
            $process = new Process($ssh);
72
            $process
73
                ->setTimeout($config['timeout'])
74
                ->setTty(true)
75
                ->mustRun();
76
77
            return $process->getOutput();
78
        }
79
80
        if ($host->isMultiplexing() === null ? $this->multiplexing : $host->isMultiplexing()) {
81
            $sshArguments = $this->initMultiplexing($host);
82
        }
83
84
        $shellCommand = $host->getShellCommand();
85
86
        if (strtolower(substr(PHP_OS, 0, 3)) === 'win') {
87
            $ssh = "ssh $sshArguments $host $become \"$shellCommand; printf '[exit_code:%s]' $?;\"";
88
        } else {
89
            $ssh = "ssh $sshArguments $host $become '$shellCommand; printf \"[exit_code:%s]\" $?;'";
90
        }
91
92
        $process = new Process($ssh);
93
        $process
94
            ->setInput($command)
95
            ->setTimeout($config['timeout']);
96
97
        $process->run($this->pop->callback($hostname));
98
99
        $output = $this->pop->filterOutput($process->getOutput());
100
        $exitCode = $this->parseExitStatus($process);
101
102
        if ($exitCode !== 0) {
103
            throw new RuntimeException(
104
                $hostname,
105
                $command,
106
                $exitCode,
107
                $output,
108
                $process->getErrorOutput()
109
            );
110
        }
111
112
        return $output;
113
    }
114
115
    private function parseExitStatus(Process $process)
116
    {
117
        $output = $process->getOutput();
118
        preg_match('/\[exit_code:(.*?)\]/', $output, $match);
119
120
        if (!isset($match[1])) {
121
            return -1;
122
        }
123
124
        $exitCode = (int)$match[1];
125
        return $exitCode;
126
    }
127
128
    private function initMultiplexing(Host $host)
129
    {
130
        $sshArguments = $host->getSshArguments()->withMultiplexing($host);
131
132
        if (!$this->isMultiplexingInitialized($host, $sshArguments)) {
133
            if ($this->output->isVeryVerbose()) {
134
                $this->pop->writeln(Process::OUT, $host->getHostname(), 'ssh multiplexing initialization');
135
            }
136
137
            $output = $this->exec("ssh -N $sshArguments $host");
138
139
            if ($this->output->isVeryVerbose()) {
140
                $this->pop->writeln(Process::OUT, $host->getHostname(), $output);
141
            }
142
        }
143
144
        return $sshArguments;
145
    }
146
147
    private function isMultiplexingInitialized(Host $host, Arguments $sshArguments)
148
    {
149
        $process = new Process("ssh -O check $sshArguments $host 2>&1");
150
        $process->run();
151
        return (bool)preg_match('/Master running/', $process->getOutput());
152
    }
153
154
    private function exec($command, &$exitCode = null)
155
    {
156
        $descriptors = [
157
            ['pipe', 'r'],
158
            ['pipe', 'w'],
159
            ['pipe', 'w'],
160
        ];
161
162
        // Don't read from stderr, there is a bug in OpenSSH_7.2p2 (stderr doesn't closed with ControlMaster)
163
164
        $process = proc_open($command, $descriptors, $pipes);
165
        if (is_resource($process)) {
166
            fclose($pipes[0]);
167
            $output = stream_get_contents($pipes[1]);
168
            fclose($pipes[1]);
169
            fclose($pipes[2]);
170
            $exitCode = proc_close($process);
171
        } else {
172
            $output = 'proc_open failure';
173
            $exitCode = 1;
174
        }
175
        return $output;
176
    }
177
}
178