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.
Completed
Push — master ( b8a420...c08840 )
by Anton
27s
created

NativeSsh::initMultiplexing()   C

Complexity

Conditions 7
Paths 48

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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