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\Component\Ssh; |
||
9 | |||
10 | use Deployer\Exception\Exception; |
||
11 | use Deployer\Host\Host; |
||
12 | use Deployer\Support\Unix; |
||
0 ignored issues
–
show
|
|||
13 | use function Deployer\Support\parse_home_dir; |
||
14 | |||
15 | class Arguments |
||
16 | { |
||
17 | /** |
||
18 | * @var array |
||
19 | */ |
||
20 | private $flags = []; |
||
21 | |||
22 | /** |
||
23 | * @var array |
||
24 | */ |
||
25 | private $options = []; |
||
26 | |||
27 | 9 | public function getCliArguments() |
|
28 | { |
||
29 | 9 | $boolFlags = array_keys(array_filter($this->flags, 'is_null')); |
|
30 | |||
31 | 9 | $valueFlags = array_filter($this->flags); |
|
32 | $valueFlags = array_map(function ($key, $value) { |
||
33 | 4 | return "$key $value"; |
|
34 | 9 | }, array_keys($valueFlags), $valueFlags); |
|
35 | |||
36 | $options = array_map(function ($key, $value) { |
||
37 | 7 | return "-o $key=$value"; |
|
38 | 9 | }, array_keys($this->options), $this->options); |
|
39 | |||
40 | 9 | $args = sprintf('%s %s %s', implode(' ', $boolFlags), implode(' ', $valueFlags), implode(' ', $options)); |
|
41 | |||
42 | 9 | return trim(preg_replace('!\s+!', ' ', $args)); |
|
43 | } |
||
44 | |||
45 | 3 | public function getOption(string $option) |
|
46 | { |
||
47 | 3 | return $this->options[$option] ?? ''; |
|
48 | } |
||
49 | |||
50 | 1 | public function getFlag(string $flag) |
|
51 | { |
||
52 | 1 | if (!array_key_exists($flag, $this->flags)) { |
|
53 | return false; |
||
54 | } |
||
55 | |||
56 | 1 | return $this->flags[$flag] ?? true; |
|
57 | } |
||
58 | |||
59 | 8 | public function withFlags(array $flags) |
|
60 | { |
||
61 | 8 | $clone = clone $this; |
|
62 | 8 | $clone->flags = $this->buildFlagsFromArray($flags); |
|
63 | |||
64 | 8 | return $clone; |
|
65 | } |
||
66 | |||
67 | 9 | public function withOptions(array $options) |
|
68 | { |
||
69 | 9 | $clone = clone $this; |
|
70 | 9 | $clone->options = $options; |
|
71 | |||
72 | 9 | return $clone; |
|
73 | } |
||
74 | |||
75 | 4 | public function withFlag(string $flag, string $value = null) |
|
76 | { |
||
77 | 4 | $clone = clone $this; |
|
78 | 4 | $clone->flags = array_merge($this->flags, [$flag => $value]); |
|
79 | |||
80 | 4 | return $clone; |
|
81 | } |
||
82 | |||
83 | 3 | public function withOption(string $option, string $value) |
|
84 | { |
||
85 | 3 | $clone = clone $this; |
|
86 | 3 | $clone->options = array_merge($this->options, [$option => $value]); |
|
87 | |||
88 | 3 | return $clone; |
|
89 | } |
||
90 | |||
91 | 4 | public function withDefaults(Arguments $defaultOptions) |
|
92 | { |
||
93 | 4 | $clone = clone $this; |
|
94 | 4 | $clone->options = array_merge($defaultOptions->options, $this->options); |
|
95 | 4 | $clone->flags = array_merge($defaultOptions->flags, $this->flags); |
|
96 | |||
97 | 4 | return $clone; |
|
98 | } |
||
99 | |||
100 | 2 | public function withMultiplexing(Host $host) |
|
101 | { |
||
102 | 2 | $controlPath = $this->generateControlPath($host); |
|
103 | |||
104 | 2 | $multiplexDefaults = (new Arguments)->withOptions([ |
|
105 | 2 | 'ControlMaster' => 'auto', |
|
106 | 2 | 'ControlPersist' => $host->get('ssh_control_persist', 60), |
|
107 | 2 | 'ControlPath' => $controlPath, |
|
108 | ]); |
||
109 | |||
110 | 2 | return $this->withDefaults($multiplexDefaults); |
|
111 | } |
||
112 | |||
113 | /** |
||
114 | * Return SSH multiplexing control path |
||
115 | * |
||
116 | * When ControlPath is longer than 104 chars we can get: |
||
117 | * |
||
118 | * SSH Error: unix_listener: too long for Unix domain socket |
||
119 | * |
||
120 | * So try to get as descriptive path as possible. |
||
121 | * %C is for creating hash out of connection attributes. |
||
122 | * |
||
123 | * @param Host $host |
||
124 | * @return string ControlPath |
||
125 | * @throws Exception |
||
126 | */ |
||
127 | 2 | private function generateControlPath(Host $host) |
|
128 | { |
||
129 | // TODO: ->addSshOption('ControlPath', '/dev/shm/deployer-%C') |
||
130 | 2 | $connectionHashLength = 16; // Length of connection hash that OpenSSH appends to controlpath |
|
131 | 2 | $unixMaxPath = 104; // Theoretical max limit for path length |
|
132 | 2 | $homeDir = parse_home_dir('~'); |
|
133 | 2 | $port = empty($host->get('port', '')) ? '' : ':' . $host->getPort(); |
|
134 | 2 | $connectionData = "{$host->getConnectionString()}$port"; |
|
135 | |||
136 | 2 | $tryLongestPossible = 0; |
|
137 | 2 | $controlPath = ''; |
|
138 | do { |
||
139 | switch ($tryLongestPossible) { |
||
140 | 2 | case 1: |
|
141 | $controlPath = "$homeDir/.ssh/deployer_%C"; |
||
142 | break; |
||
143 | 2 | case 2: |
|
144 | $controlPath = "$homeDir/.ssh/" . hash("crc32", $connectionData); |
||
145 | break; |
||
146 | 2 | case 3: |
|
147 | throw new Exception("The multiplexing control path is too long. Control path is: $controlPath"); |
||
148 | default: |
||
149 | 2 | $controlPath = "$homeDir/.ssh/deployer_$connectionData"; |
|
150 | } |
||
151 | 2 | $tryLongestPossible++; |
|
152 | 2 | } while (strlen($controlPath) + $connectionHashLength > $unixMaxPath); // Unix socket max length |
|
153 | |||
154 | 2 | return $controlPath; |
|
155 | } |
||
156 | |||
157 | 8 | private function buildFlagsFromArray($flags) |
|
158 | { |
||
159 | $boolFlags = array_filter(array_map(function ($key, $value) { |
||
160 | 8 | if (is_int($key)) { |
|
161 | 7 | return $value; |
|
162 | } |
||
163 | |||
164 | 3 | if (null === $value) { |
|
165 | 2 | return $key; |
|
166 | } |
||
167 | |||
168 | 3 | return false; |
|
169 | 8 | }, array_keys($flags), $flags)); |
|
170 | |||
171 | $valueFlags = array_filter($flags, function ($key, $value) { |
||
172 | 8 | return is_string($key) && is_string($value); |
|
173 | 8 | }, ARRAY_FILTER_USE_BOTH); |
|
174 | |||
175 | 8 | return array_merge(array_fill_keys($boolFlags, null), $valueFlags); |
|
176 | } |
||
177 | |||
178 | public function __toString() |
||
179 | { |
||
180 | return $this->getCliArguments(); |
||
181 | } |
||
182 | } |
||
183 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths