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
Pull Request — master (#1051)
by Maxim
02:52
created

NativeSsh   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 274
Duplicated Lines 3.65 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 0%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 10
loc 274
ccs 0
cts 173
cp 0
rs 8.3673
wmc 45
lcom 1
cbo 3

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A connect() 0 4 1
D run() 5 54 11
A upload() 0 19 4
A download() 0 9 3
C scpCopy() 0 38 7
A getConfiguration() 0 4 1
C getConnectionHash() 0 34 8
A getMultiplexingSshOptions() 0 8 1
C initMultiplexing() 5 28 8

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like NativeSsh often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use NativeSsh, and based on these observations, apply Extract Interface, too.

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\Server\Remote;
9
10
use Deployer\Server\Configuration;
11
use Deployer\Server\ServerInterface;
12
use Symfony\Component\Process\Exception\ProcessFailedException;
13
use Symfony\Component\Process\Process;
14
15
class NativeSsh implements ServerInterface
16
{
17
    const UNIX_SOCKET_MAX_LENGTH = 104;
18
19
    /**
20
     * @var array
21
     */
22
    private $mkdirs = [];
23
24
    /**
25
     * @var Configuration
26
     */
27
    private $configuration;
28
29
    /**
30
     * @param Configuration $configuration
31
     */
32
    public function __construct(Configuration $configuration)
33
    {
34
        $this->configuration = $configuration;
35
    }
36
37
    /**
38
     * {@inheritdoc}
39
     */
40
    public function connect()
41
    {
42
        /* No persistent connection is used */
43
    }
44
45
    /**
46
     * {@inheritdoc}
47
     */
48
    public function run($command)
49
    {
50
        $serverConfig = $this->getConfiguration();
51
        $sshOptions = [
52
            '-A',
53
            '-o UserKnownHostsFile=/dev/null',
54
            '-o StrictHostKeyChecking=no'
55
        ];
56
57
        if (\Deployer\get('ssh_multiplexing', false)) {
58
            $this->initMultiplexing();
59
            $sshOptions = array_merge($sshOptions, $this->getMultiplexingSshOptions());
60
        }
61
62
        $username = $serverConfig->getUser() ? $serverConfig->getUser() : null;
63
        if (!empty($username)) {
64
            $username = $username . '@';
65
        }
66
        $hostname = $serverConfig->getHost();
67
68
        if ($serverConfig->getConfigFile()) {
69
            $sshOptions[] = '-F ' . escapeshellarg($serverConfig->getConfigFile());
70
        }
71
72
        if ($serverConfig->getPort()) {
73
            $sshOptions[] = '-p ' . escapeshellarg($serverConfig->getPort());
74
        }
75
76 View Code Duplication
        if ($serverConfig->getPrivateKey()) {
77
            $sshOptions[] = '-i ' . escapeshellarg($serverConfig->getPrivateKey());
78
        } elseif ($serverConfig->getPemFile()) {
79
            $sshOptions[] = '-i ' . escapeshellarg($serverConfig->getPemFile());
80
        }
81
82
        if ($serverConfig->getPty()) {
83
            $sshOptions[] = '-t';
84
        }
85
86
        $sshCommand = 'ssh ' . implode(' ', $sshOptions) . ' ' . escapeshellarg($username . $hostname) . ' ' . escapeshellarg($command);
87
88
        try {
89
            $process = new Process($sshCommand);
90
            $process
91
                ->setPty($serverConfig->getPty())
92
                ->setTimeout(null)
93
                ->setIdleTimeout(null)
94
                ->mustRun();
95
        } catch (ProcessFailedException $exception) {
96
            $errorMessage = \Deployer\isDebug() ? $exception->getMessage() : $process->getErrorOutput();
97
            throw new \RuntimeException($errorMessage);
98
        }
99
100
        return $process->getOutput();
101
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106
    public function upload($local, $remote)
107
    {
108
        $local = str_replace(' ', '\ ', $local);
109
        $remote = str_replace(' ', '\ ', $remote);
110
111
        $serverConfig = $this->getConfiguration();
112
113
        $username = $serverConfig->getUser() ? $serverConfig->getUser() : null;
114
        $hostname = $serverConfig->getHost();
115
116
        $dir = dirname($remote);
117
118
        if (!in_array($dir, $this->mkdirs)) {
119
            $this->run('mkdir -p ' . escapeshellarg($dir));
120
            $this->mkdirs[] = $dir;
121
        }
122
123
        return $this->scpCopy($local, (!empty($username) ? $username . '@' : '') . $hostname . ':' . $remote);
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129
    public function download($local, $remote)
130
    {
131
        $serverConfig = $this->getConfiguration();
132
133
        $username = $serverConfig->getUser() ? $serverConfig->getUser() : null;
134
        $hostname = $serverConfig->getHost();
135
136
        return $this->scpCopy((!empty($username) ? $username . '@' : '') . $hostname . ':' . $remote, $local);
137
    }
138
139
    /**
140
     * Copy file from target1 to target 2 via scp
141
     * @param string $target
142
     * @param string $target2
143
     * @return string
144
     */
145
    public function scpCopy($target, $target2)
146
    {
147
        $serverConfig = $this->getConfiguration();
148
149
        $scpOptions = [];
150
151
        if ($serverConfig->getConfigFile()) {
152
            $scpOptions[] = '-F ' . escapeshellarg($serverConfig->getConfigFile());
153
        }
154
155
        if ($serverConfig->getPort()) {
156
            $scpOptions[] = '-P ' . escapeshellarg($serverConfig->getPort());
157
        }
158
159
        if ($serverConfig->getPrivateKey()) {
160
            $scpOptions[] = '-i ' . escapeshellarg($serverConfig->getPrivateKey());
161
        }
162
163
        if (\Deployer\get('ssh_multiplexing', false)) {
164
            $this->initMultiplexing();
165
            $scpOptions = array_merge($scpOptions, $this->getMultiplexingSshOptions());
166
        }
167
168
        $scpCommand = 'scp ' . implode(' ', $scpOptions) . ' ' . escapeshellarg($target) . ' ' . escapeshellarg($target2);
169
170
        try {
171
            $process = new Process($scpCommand);
172
            $process
173
                ->setTimeout(null)
174
                ->setIdleTimeout(null)
175
                ->mustRun();
176
        } catch (ProcessFailedException $exception) {
177
            $errorMessage = \Deployer\isDebug() ? $exception->getMessage() : $process->getErrorOutput();
178
            throw new \RuntimeException($errorMessage);
179
        }
180
181
        return $process->getOutput();
182
    }
183
184
    /**
185
     * @return Configuration
186
     */
187
    public function getConfiguration()
188
    {
189
        return $this->configuration;
190
    }
191
192
    /**
193
     * Return ssh multiplexing socket name
194
     *
195
     * When $connectionHash is longer than 104 chars we can get "SSH Error: unix_listener: too long for Unix domain socket".
196
     * https://github.com/ansible/ansible/issues/11536
197
     * So try to get as descriptive hash as possible.
198
     * %C is creating hash out of connection attributes.
199
     *
200
     * @return string Ssh multiplexing socket name
201
     */
202
    protected function getConnectionHash()
203
    {
204
        $serverConfig = $this->getConfiguration();
205
        $connectionData = "{$serverConfig->getUser()}@{$serverConfig->getHost()}:{$serverConfig->getPort()}";
206
        $tryLongestPossibleSocketName = 0;
207
208
        $connectionHash = '';
209
        do {
210
            switch ($tryLongestPossibleSocketName) {
211
                case 1:
212
                    $connectionHash = "~/.ssh/deployer_mux_" . $connectionData;
213
                    break;
214
                case 2:
215
                    $connectionHash = "~/.ssh/deployer_mux_%C";
216
                    break;
217
                case 3:
218
                    $connectionHash = "~/deployer_mux_$connectionData";
219
                    break;
220
                case 4:
221
                    $connectionHash = "~/deployer_mux_%C";
222
                    break;
223
                case 5:
224
                    $connectionHash = "~/mux_%C";
225
                    break;
226
                case 6:
227
                    throw new \RuntimeException("The multiplexing socket name is too long. Socket name is:" . $connectionHash);
228
                default:
229
                    $connectionHash = "~/.ssh/deployer_mux_$connectionData";
230
            }
231
            $tryLongestPossibleSocketName++;
232
        } while (strlen($connectionHash) > self::UNIX_SOCKET_MAX_LENGTH);
233
234
        return $connectionHash;
235
    }
236
237
238
    /**
239
     * Return ssh options for multiplexing
240
     *
241
     * @return string[]
242
     */
243
    protected function getMultiplexingSshOptions()
244
    {
245
        return [
246
            '-o ControlMaster=auto',
247
            '-o ControlPersist=5',
248
            '-o ControlPath=\'' . $this->getConnectionHash() . '\'',
249
        ];
250
    }
251
252
253
    /**
254
     * Init multiplexing with exec() command
255
     *
256
     * Background: Symfony Process hangs on creating multiplex connection
257
     * but after mux is created with exec() then Symfony Process
258
     * can work with it.
259
     */
260
    public function initMultiplexing()
261
    {
262
        $serverConfig = $this->getConfiguration();
263
        $username = $serverConfig->getUser() ? $serverConfig->getUser() . '@' : null;
264
        $hostname = $serverConfig->getHost();
265
266
        $sshOptions = [];
267
        if ($serverConfig->getConfigFile()) {
268
            $sshOptions[] = '-F ' . escapeshellarg($serverConfig->getConfigFile());
269
        }
270
        if ($serverConfig->getPort()) {
271
            $sshOptions[] = '-p ' . escapeshellarg($serverConfig->getPort());
272
        }
273 View Code Duplication
        if ($serverConfig->getPrivateKey()) {
274
            $sshOptions[] = '-i ' . escapeshellarg($serverConfig->getPrivateKey());
275
        } elseif ($serverConfig->getPemFile()) {
276
            $sshOptions[] = '-i ' . escapeshellarg($serverConfig->getPemFile());
277
        }
278
        $sshOptions = array_merge($sshOptions, $this->getMultiplexingSshOptions());
279
280
        exec('ssh ' . implode(' ', $sshOptions) . ' -O check -S ' . $this->getConnectionHash() . ' ' . escapeshellarg($username . $hostname) . ' 2>&1', $checkifMuxActive);
281
        if (!preg_match('/Master running/', $checkifMuxActive[0])) {
282
            if (\Deployer\isVerbose()) {
283
                \Deployer\writeln('  SSH multiplexing initialization');
284
            }
285
            exec('ssh ' . implode(' ', $sshOptions) . ' ' . escapeshellarg($username . $hostname) . "  'echo \"SSH multiplexing initialization\"' ");
286
        }
287
    }
288
}
289