Arguments   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 166
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 89.74%

Importance

Changes 0
Metric Value
dl 0
loc 166
ccs 70
cts 78
cp 0.8974
rs 10
c 0
b 0
f 0
wmc 21
lcom 1
cbo 3

12 Methods

Rating   Name   Duplication   Size   Complexity  
A getCliArguments() 0 17 1
A getOption() 0 4 1
A getFlag() 0 8 2
A withFlags() 0 7 1
A withOptions() 0 7 1
A withFlag() 0 7 1
A withOption() 0 7 1
A withDefaults() 0 8 1
A withMultiplexing() 0 12 1
B generateControlPath() 0 27 6
A buildFlagsFromArray() 0 20 4
A __toString() 0 4 1
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\Exception\Exception;
11
use Deployer\Host\Host;
12
use Deployer\Support\Unix;
13
14
/**
15
 * @author Michael Woodward <[email protected]>
16
 */
17
class Arguments
18
{
19
    /**
20
     * @var array
21
     */
22
    private $flags = [];
23
24
    /**
25
     * @var array
26
     */
27
    private $options = [];
28
29 9
    public function getCliArguments()
30
    {
31 9
        $boolFlags = array_keys(array_filter($this->flags, 'is_null'));
32
33 9
        $valueFlags = array_filter($this->flags);
34 9
        $valueFlags = array_map(function ($key, $value) {
35 4
            return "$key $value";
36 9
        }, array_keys($valueFlags), $valueFlags);
37
38 9
        $options = array_map(function ($key, $value) {
39 7
            return "-o $key=$value";
40 9
        }, array_keys($this->options), $this->options);
41
42 9
        $args = sprintf('%s %s %s', implode(' ', $boolFlags), implode(' ', $valueFlags), implode(' ', $options));
43
44 9
        return trim(preg_replace('!\s+!', ' ', $args));
45
    }
46
47 3
    public function getOption(string $option)
48
    {
49 3
        return $this->options[$option] ?? '';
50
    }
51
52 1
    public function getFlag(string $flag)
53
    {
54 1
        if (!array_key_exists($flag, $this->flags)) {
55
            return false;
56
        }
57
58 1
        return $this->flags[$flag] ?? true;
59
    }
60
61 8
    public function withFlags(array $flags)
62
    {
63 8
        $clone = clone $this;
64 8
        $clone->flags = $this->buildFlagsFromArray($flags);
65
66 8
        return $clone;
67
    }
68
69 10
    public function withOptions(array $options)
70
    {
71 10
        $clone = clone $this;
72 10
        $clone->options = $options;
73
74 10
        return $clone;
75
    }
76
77 4
    public function withFlag(string $flag, string $value = null)
78
    {
79 4
        $clone = clone $this;
80 4
        $clone->flags = array_merge($this->flags, [$flag => $value]);
81
82 4
        return $clone;
83
    }
84
85 4
    public function withOption(string $option, string $value)
86
    {
87 4
        $clone = clone $this;
88 4
        $clone->options = array_merge($this->options, [$option => $value]);
89
90 4
        return $clone;
91
    }
92
93 4
    public function withDefaults(Arguments $defaultOptions)
94
    {
95 4
        $clone = clone $this;
96 4
        $clone->options = array_merge($defaultOptions->options, $this->options);
97 4
        $clone->flags = array_merge($defaultOptions->flags, $this->flags);
98
99 4
        return $clone;
100
    }
101
102 2
    public function withMultiplexing(Host $host)
103
    {
104 2
        $controlPath = $this->generateControlPath($host);
105
106 2
        $multiplexDefaults = (new Arguments)->withOptions([
107 2
            'ControlMaster' => 'auto',
108 2
            'ControlPersist' => '60',
109 2
            'ControlPath' => $controlPath,
110
        ]);
111
112 2
        return $this->withDefaults($multiplexDefaults);
113
    }
114
115
    /**
116
     * Return SSH multiplexing control path
117
     *
118
     * When ControlPath is longer than 104 chars we can get:
119
     *
120
     *     SSH Error: unix_listener: too long for Unix domain socket
121
     *
122
     * So try to get as descriptive path as possible.
123
     * %C is for creating hash out of connection attributes.
124
     *
125
     * @param Host $host
126
     * @return string ControlPath
127
     * @throws Exception
128
     */
129 2
    private function generateControlPath(Host $host)
130
    {
131 2
        $connectionHashLength = 16; // Length of connection hash that OpenSSH appends to controlpath
132 2
        $unixMaxPath = 104; // Theoretical max limit for path length
133 2
        $homeDir = Unix::parseHomeDir('~');
134 2
        $port = empty($host->getPort()) ? '' : ':' . $host->getPort();
135 2
        $connectionData = "$host$port";
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/mux_%C";
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
    private function buildFlagsFromArray($flags)
158
    {
159 8
        $boolFlags = array_filter(array_map(function ($key, $value) {
160 8
            if (is_int($key)) {
161 6
                return $value;
162
            }
163
164 4
            if (null === $value) {
165 3
                return $key;
166
            }
167
168 4
            return false;
169 8
        }, array_keys($flags), $flags));
170
171 8
        $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